summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPradyun Gedam <pradyunsg@gmail.com>2023-03-27 11:52:58 +0100
committerGitHub <noreply@github.com>2023-03-27 11:52:58 +0100
commit01b13881659ead1b697de4fa33f6470dc379c9da (patch)
tree100809d6fdb5f54629f1682cfe3976241d58efa2 /src
parentae9c0fd8a8cf42f70534ae7ec4ae865735389eb8 (diff)
parent440487792ca9d440e5cfdc402e77f8c793a2da70 (diff)
downloadpip-01b13881659ead1b697de4fa33f6470dc379c9da.tar.gz
Merge branch 'main' into main
Diffstat (limited to 'src')
-rw-r--r--src/pip/__init__.py2
-rw-r--r--src/pip/__pip-runner__.py50
-rw-r--r--src/pip/_internal/build_env.py126
-rw-r--r--src/pip/_internal/cache.py31
-rw-r--r--src/pip/_internal/cli/autocompletion.py10
-rw-r--r--src/pip/_internal/cli/base_command.py16
-rw-r--r--src/pip/_internal/cli/cmdoptions.py147
-rw-r--r--src/pip/_internal/cli/command_context.py4
-rw-r--r--src/pip/_internal/cli/main_parser.py49
-rw-r--r--src/pip/_internal/cli/parser.py6
-rw-r--r--src/pip/_internal/cli/progress_bars.py298
-rw-r--r--src/pip/_internal/cli/req_command.py84
-rw-r--r--src/pip/_internal/cli/spinners.py14
-rw-r--r--src/pip/_internal/commands/__init__.py5
-rw-r--r--src/pip/_internal/commands/cache.py8
-rw-r--r--src/pip/_internal/commands/completion.py30
-rw-r--r--src/pip/_internal/commands/configuration.py30
-rw-r--r--src/pip/_internal/commands/debug.py13
-rw-r--r--src/pip/_internal/commands/download.py11
-rw-r--r--src/pip/_internal/commands/index.py1
-rw-r--r--src/pip/_internal/commands/inspect.py92
-rw-r--r--src/pip/_internal/commands/install.py196
-rw-r--r--src/pip/_internal/commands/list.py12
-rw-r--r--src/pip/_internal/commands/show.py78
-rw-r--r--src/pip/_internal/commands/uninstall.py16
-rw-r--r--src/pip/_internal/commands/wheel.py43
-rw-r--r--src/pip/_internal/configuration.py30
-rw-r--r--src/pip/_internal/distributions/base.py5
-rw-r--r--src/pip/_internal/distributions/installed.py9
-rw-r--r--src/pip/_internal/distributions/sdist.py35
-rw-r--r--src/pip/_internal/distributions/wheel.py5
-rw-r--r--src/pip/_internal/exceptions.py399
-rw-r--r--src/pip/_internal/index/collector.py341
-rw-r--r--src/pip/_internal/index/package_finder.py170
-rw-r--r--src/pip/_internal/locations/__init__.py117
-rw-r--r--src/pip/_internal/locations/_distutils.py28
-rw-r--r--src/pip/_internal/locations/_sysconfig.py10
-rw-r--r--src/pip/_internal/locations/base.py31
-rw-r--r--src/pip/_internal/metadata/__init__.py92
-rw-r--r--src/pip/_internal/metadata/_json.py84
-rw-r--r--src/pip/_internal/metadata/base.py388
-rw-r--r--src/pip/_internal/metadata/importlib/__init__.py4
-rw-r--r--src/pip/_internal/metadata/importlib/_compat.py55
-rw-r--r--src/pip/_internal/metadata/importlib/_dists.py224
-rw-r--r--src/pip/_internal/metadata/importlib/_envs.py188
-rw-r--r--src/pip/_internal/metadata/pkg_resources.py190
-rw-r--r--src/pip/_internal/models/direct_url.py28
-rw-r--r--src/pip/_internal/models/installation_report.py53
-rw-r--r--src/pip/_internal/models/link.py298
-rw-r--r--src/pip/_internal/models/search_scope.py6
-rw-r--r--src/pip/_internal/models/wheel.py9
-rw-r--r--src/pip/_internal/network/auth.py352
-rw-r--r--src/pip/_internal/network/cache.py6
-rw-r--r--src/pip/_internal/network/download.py14
-rw-r--r--src/pip/_internal/network/lazy_wheel.py18
-rw-r--r--src/pip/_internal/network/session.py76
-rw-r--r--src/pip/_internal/network/utils.py4
-rw-r--r--src/pip/_internal/operations/build/build_tracker.py (renamed from src/pip/_internal/req/req_tracker.py)20
-rw-r--r--src/pip/_internal/operations/build/metadata.py17
-rw-r--r--src/pip/_internal/operations/build/metadata_editable.py15
-rw-r--r--src/pip/_internal/operations/build/metadata_legacy.py21
-rw-r--r--src/pip/_internal/operations/build/wheel.py4
-rw-r--r--src/pip/_internal/operations/build/wheel_editable.py4
-rw-r--r--src/pip/_internal/operations/build/wheel_legacy.py9
-rw-r--r--src/pip/_internal/operations/check.py2
-rw-r--r--src/pip/_internal/operations/freeze.py4
-rw-r--r--src/pip/_internal/operations/install/editable_legacy.py6
-rw-r--r--src/pip/_internal/operations/install/legacy.py15
-rw-r--r--src/pip/_internal/operations/install/wheel.py38
-rw-r--r--src/pip/_internal/operations/prepare.py227
-rw-r--r--src/pip/_internal/pyproject.py63
-rw-r--r--src/pip/_internal/req/__init__.py6
-rw-r--r--src/pip/_internal/req/constructors.py76
-rw-r--r--src/pip/_internal/req/req_file.py35
-rw-r--r--src/pip/_internal/req/req_install.py189
-rw-r--r--src/pip/_internal/req/req_set.py135
-rw-r--r--src/pip/_internal/req/req_uninstall.py274
-rw-r--r--src/pip/_internal/resolution/legacy/resolver.py143
-rw-r--r--src/pip/_internal/resolution/resolvelib/candidates.py39
-rw-r--r--src/pip/_internal/resolution/resolvelib/factory.py52
-rw-r--r--src/pip/_internal/resolution/resolvelib/provider.py120
-rw-r--r--src/pip/_internal/resolution/resolvelib/reporter.py30
-rw-r--r--src/pip/_internal/resolution/resolvelib/resolver.py77
-rw-r--r--src/pip/_internal/self_outdated_check.py228
-rw-r--r--src/pip/_internal/utils/_jaraco_text.py109
-rw-r--r--src/pip/_internal/utils/deprecation.py55
-rw-r--r--src/pip/_internal/utils/distutils_args.py42
-rw-r--r--src/pip/_internal/utils/egg_link.py7
-rw-r--r--src/pip/_internal/utils/encoding.py2
-rw-r--r--src/pip/_internal/utils/entrypoints.py57
-rw-r--r--src/pip/_internal/utils/filesystem.py33
-rw-r--r--src/pip/_internal/utils/hashes.py6
-rw-r--r--src/pip/_internal/utils/logging.py166
-rw-r--r--src/pip/_internal/utils/misc.py186
-rw-r--r--src/pip/_internal/utils/packaging.py57
-rw-r--r--src/pip/_internal/utils/parallel.py103
-rw-r--r--src/pip/_internal/utils/pkg_resources.py33
-rw-r--r--src/pip/_internal/utils/setuptools_build.py66
-rw-r--r--src/pip/_internal/utils/subprocess.py81
-rw-r--r--src/pip/_internal/utils/temp_dir.py8
-rw-r--r--src/pip/_internal/utils/unpacking.py3
-rw-r--r--src/pip/_internal/utils/virtualenv.py12
-rw-r--r--src/pip/_internal/utils/wheel.py48
-rw-r--r--src/pip/_internal/vcs/bazaar.py27
-rw-r--r--src/pip/_internal/vcs/git.py21
-rw-r--r--src/pip/_internal/vcs/mercurial.py18
-rw-r--r--src/pip/_internal/vcs/subversion.py14
-rw-r--r--src/pip/_internal/vcs/versioncontrol.py28
-rw-r--r--src/pip/_internal/wheel_builder.py39
-rw-r--r--src/pip/_vendor/README.rst3
-rw-r--r--src/pip/_vendor/__init__.py11
-rw-r--r--src/pip/_vendor/cachecontrol/LICENSE.txt6
-rw-r--r--src/pip/_vendor/cachecontrol/__init__.py9
-rw-r--r--src/pip/_vendor/cachecontrol/_cmd.py4
-rw-r--r--src/pip/_vendor/cachecontrol/adapter.py10
-rw-r--r--src/pip/_vendor/cachecontrol/cache.py30
-rw-r--r--src/pip/_vendor/cachecontrol/caches/__init__.py11
-rw-r--r--src/pip/_vendor/cachecontrol/caches/file_cache.py58
-rw-r--r--src/pip/_vendor/cachecontrol/caches/redis_cache.py8
-rw-r--r--src/pip/_vendor/cachecontrol/compat.py5
-rw-r--r--src/pip/_vendor/cachecontrol/controller.py111
-rw-r--r--src/pip/_vendor/cachecontrol/filewrapper.py39
-rw-r--r--src/pip/_vendor/cachecontrol/heuristics.py4
-rw-r--r--src/pip/_vendor/cachecontrol/serialize.py52
-rw-r--r--src/pip/_vendor/cachecontrol/wrapper.py4
-rw-r--r--src/pip/_vendor/certifi.pyi1
-rw-r--r--src/pip/_vendor/certifi/LICENSE2
-rw-r--r--src/pip/_vendor/certifi/__init__.py3
-rw-r--r--src/pip/_vendor/certifi/cacert.pem1234
-rw-r--r--src/pip/_vendor/certifi/core.py86
-rw-r--r--src/pip/_vendor/certifi/py.typed (renamed from src/pip/_vendor/html5lib/filters/__init__.py)0
-rw-r--r--src/pip/_vendor/chardet.pyi1
-rw-r--r--src/pip/_vendor/chardet/LICENSE20
-rw-r--r--src/pip/_vendor/chardet/__init__.py100
-rw-r--r--src/pip/_vendor/chardet/big5freq.py6
-rw-r--r--src/pip/_vendor/chardet/big5prober.py12
-rw-r--r--src/pip/_vendor/chardet/chardistribution.py148
-rw-r--r--src/pip/_vendor/chardet/charsetgroupprober.py45
-rw-r--r--src/pip/_vendor/chardet/charsetprober.py74
-rw-r--r--src/pip/_vendor/chardet/cli/__init__.py1
-rw-r--r--src/pip/_vendor/chardet/cli/chardetect.py90
-rw-r--r--src/pip/_vendor/chardet/codingstatemachine.py32
-rw-r--r--src/pip/_vendor/chardet/codingstatemachinedict.py19
-rw-r--r--src/pip/_vendor/chardet/cp949prober.py8
-rw-r--r--src/pip/_vendor/chardet/enums.py23
-rw-r--r--src/pip/_vendor/chardet/escprober.py45
-rw-r--r--src/pip/_vendor/chardet/escsm.py385
-rw-r--r--src/pip/_vendor/chardet/eucjpprober.py60
-rw-r--r--src/pip/_vendor/chardet/euckrfreq.py3
-rw-r--r--src/pip/_vendor/chardet/euckrprober.py12
-rw-r--r--src/pip/_vendor/chardet/euctwfreq.py677
-rw-r--r--src/pip/_vendor/chardet/euctwprober.py13
-rw-r--r--src/pip/_vendor/chardet/gb2312freq.py3
-rw-r--r--src/pip/_vendor/chardet/gb2312prober.py13
-rw-r--r--src/pip/_vendor/chardet/hebrewprober.py110
-rw-r--r--src/pip/_vendor/chardet/jisfreq.py4
-rw-r--r--src/pip/_vendor/chardet/johabfreq.py2382
-rw-r--r--src/pip/_vendor/chardet/johabprober.py (renamed from src/pip/_vendor/chardet/compat.py)41
-rw-r--r--src/pip/_vendor/chardet/jpcntx.py219
-rw-r--r--src/pip/_vendor/chardet/langbulgarianmodel.py1061
-rw-r--r--src/pip/_vendor/chardet/langgreekmodel.py1061
-rw-r--r--src/pip/_vendor/chardet/langhebrewmodel.py533
-rw-r--r--src/pip/_vendor/chardet/langhungarianmodel.py1061
-rw-r--r--src/pip/_vendor/chardet/langrussianmodel.py3173
-rw-r--r--src/pip/_vendor/chardet/langthaimodel.py533
-rw-r--r--src/pip/_vendor/chardet/langturkishmodel.py533
-rw-r--r--src/pip/_vendor/chardet/latin1prober.py44
-rw-r--r--src/pip/_vendor/chardet/macromanprober.py162
-rw-r--r--src/pip/_vendor/chardet/mbcharsetprober.py62
-rw-r--r--src/pip/_vendor/chardet/mbcsgroupprober.py19
-rw-r--r--src/pip/_vendor/chardet/mbcssm.py813
-rw-r--r--src/pip/_vendor/chardet/metadata/languages.py574
-rw-r--r--src/pip/_vendor/chardet/py.typed0
-rw-r--r--src/pip/_vendor/chardet/resultdict.py16
-rw-r--r--src/pip/_vendor/chardet/sbcharsetprober.py95
-rw-r--r--src/pip/_vendor/chardet/sbcsgroupprober.py33
-rw-r--r--src/pip/_vendor/chardet/sjisprober.py65
-rw-r--r--src/pip/_vendor/chardet/universaldetector.py236
-rw-r--r--src/pip/_vendor/chardet/utf1632prober.py225
-rw-r--r--src/pip/_vendor/chardet/utf8prober.py32
-rw-r--r--src/pip/_vendor/chardet/version.py6
-rw-r--r--src/pip/_vendor/colorama/__init__.py5
-rw-r--r--src/pip/_vendor/colorama/ansitowin32.py27
-rw-r--r--src/pip/_vendor/colorama/initialise.py51
-rw-r--r--src/pip/_vendor/colorama/tests/__init__.py1
-rw-r--r--src/pip/_vendor/colorama/tests/ansi_test.py76
-rw-r--r--src/pip/_vendor/colorama/tests/ansitowin32_test.py294
-rw-r--r--src/pip/_vendor/colorama/tests/initialise_test.py189
-rw-r--r--src/pip/_vendor/colorama/tests/isatty_test.py57
-rw-r--r--src/pip/_vendor/colorama/tests/utils.py49
-rw-r--r--src/pip/_vendor/colorama/tests/winterm_test.py131
-rw-r--r--src/pip/_vendor/colorama/win32.py28
-rw-r--r--src/pip/_vendor/colorama/winterm.py28
-rw-r--r--src/pip/_vendor/distlib/__init__.py4
-rw-r--r--src/pip/_vendor/distlib/_backport/__init__.py6
-rw-r--r--src/pip/_vendor/distlib/_backport/misc.py41
-rw-r--r--src/pip/_vendor/distlib/_backport/shutil.py764
-rw-r--r--src/pip/_vendor/distlib/_backport/sysconfig.cfg84
-rw-r--r--src/pip/_vendor/distlib/_backport/sysconfig.py786
-rw-r--r--src/pip/_vendor/distlib/_backport/tarfile.py2607
-rw-r--r--src/pip/_vendor/distlib/compat.py28
-rw-r--r--src/pip/_vendor/distlib/database.py67
-rw-r--r--src/pip/_vendor/distlib/index.py13
-rw-r--r--src/pip/_vendor/distlib/locators.py6
-rw-r--r--src/pip/_vendor/distlib/markers.py9
-rw-r--r--src/pip/_vendor/distlib/metadata.py48
-rw-r--r--src/pip/_vendor/distlib/scripts.py10
-rw-r--r--src/pip/_vendor/distlib/t32.exebin96768 -> 97792 bytes
-rw-r--r--src/pip/_vendor/distlib/t64-arm.exebin180736 -> 182784 bytes
-rw-r--r--src/pip/_vendor/distlib/t64.exebin105984 -> 108032 bytes
-rw-r--r--src/pip/_vendor/distlib/util.py81
-rw-r--r--src/pip/_vendor/distlib/w32.exebin90112 -> 91648 bytes
-rw-r--r--src/pip/_vendor/distlib/w64-arm.exebin166400 -> 168448 bytes
-rw-r--r--src/pip/_vendor/distlib/w64.exebin99840 -> 101888 bytes
-rw-r--r--src/pip/_vendor/distlib/wheel.py51
-rw-r--r--src/pip/_vendor/distro/LICENSE (renamed from src/pip/_vendor/distro.LICENSE)0
-rw-r--r--src/pip/_vendor/distro/__init__.py54
-rw-r--r--src/pip/_vendor/distro/__main__.py4
-rw-r--r--src/pip/_vendor/distro/distro.py (renamed from src/pip/_vendor/distro.py)509
-rw-r--r--src/pip/_vendor/distro/py.typed0
-rw-r--r--src/pip/_vendor/html5lib.pyi1
-rw-r--r--src/pip/_vendor/html5lib/LICENSE20
-rw-r--r--src/pip/_vendor/html5lib/__init__.py35
-rw-r--r--src/pip/_vendor/html5lib/_ihatexml.py289
-rw-r--r--src/pip/_vendor/html5lib/_inputstream.py918
-rw-r--r--src/pip/_vendor/html5lib/_tokenizer.py1735
-rw-r--r--src/pip/_vendor/html5lib/_trie/__init__.py5
-rw-r--r--src/pip/_vendor/html5lib/_trie/_base.py40
-rw-r--r--src/pip/_vendor/html5lib/_trie/py.py67
-rw-r--r--src/pip/_vendor/html5lib/_utils.py159
-rw-r--r--src/pip/_vendor/html5lib/constants.py2946
-rw-r--r--src/pip/_vendor/html5lib/filters/alphabeticalattributes.py29
-rw-r--r--src/pip/_vendor/html5lib/filters/base.py12
-rw-r--r--src/pip/_vendor/html5lib/filters/inject_meta_charset.py73
-rw-r--r--src/pip/_vendor/html5lib/filters/lint.py93
-rw-r--r--src/pip/_vendor/html5lib/filters/optionaltags.py207
-rw-r--r--src/pip/_vendor/html5lib/filters/sanitizer.py916
-rw-r--r--src/pip/_vendor/html5lib/filters/whitespace.py38
-rw-r--r--src/pip/_vendor/html5lib/html5parser.py2795
-rw-r--r--src/pip/_vendor/html5lib/serializer.py409
-rw-r--r--src/pip/_vendor/html5lib/treeadapters/__init__.py30
-rw-r--r--src/pip/_vendor/html5lib/treeadapters/genshi.py54
-rw-r--r--src/pip/_vendor/html5lib/treeadapters/sax.py50
-rw-r--r--src/pip/_vendor/html5lib/treebuilders/__init__.py88
-rw-r--r--src/pip/_vendor/html5lib/treebuilders/base.py417
-rw-r--r--src/pip/_vendor/html5lib/treebuilders/dom.py239
-rw-r--r--src/pip/_vendor/html5lib/treebuilders/etree.py343
-rw-r--r--src/pip/_vendor/html5lib/treebuilders/etree_lxml.py392
-rw-r--r--src/pip/_vendor/html5lib/treewalkers/__init__.py154
-rw-r--r--src/pip/_vendor/html5lib/treewalkers/base.py252
-rw-r--r--src/pip/_vendor/html5lib/treewalkers/dom.py43
-rw-r--r--src/pip/_vendor/html5lib/treewalkers/etree.py131
-rw-r--r--src/pip/_vendor/html5lib/treewalkers/etree_lxml.py215
-rw-r--r--src/pip/_vendor/html5lib/treewalkers/genshi.py69
-rw-r--r--src/pip/_vendor/idna/codec.py15
-rw-r--r--src/pip/_vendor/idna/compat.py9
-rw-r--r--src/pip/_vendor/idna/core.py71
-rw-r--r--src/pip/_vendor/idna/idnadata.py165
-rw-r--r--src/pip/_vendor/idna/intranges.py12
-rw-r--r--src/pip/_vendor/idna/package_data.py2
-rw-r--r--src/pip/_vendor/idna/uts46data.py998
-rw-r--r--src/pip/_vendor/msgpack/__init__.py5
-rw-r--r--src/pip/_vendor/msgpack/_version.py1
-rw-r--r--src/pip/_vendor/msgpack/ext.py8
-rw-r--r--src/pip/_vendor/msgpack/fallback.py289
-rw-r--r--src/pip/_vendor/packaging/__about__.py2
-rw-r--r--src/pip/_vendor/packaging/_musllinux.py2
-rw-r--r--src/pip/_vendor/packaging/_structures.py6
-rw-r--r--src/pip/_vendor/packaging/specifiers.py30
-rw-r--r--src/pip/_vendor/packaging/tags.py23
-rw-r--r--src/pip/_vendor/pep517.pyi1
-rw-r--r--src/pip/_vendor/pep517/__init__.py6
-rw-r--r--src/pip/_vendor/pep517/build.py127
-rw-r--r--src/pip/_vendor/pep517/check.py207
-rw-r--r--src/pip/_vendor/pep517/colorlog.py115
-rw-r--r--src/pip/_vendor/pep517/compat.py51
-rw-r--r--src/pip/_vendor/pep517/dirtools.py44
-rw-r--r--src/pip/_vendor/pep517/envbuild.py171
-rw-r--r--src/pip/_vendor/pep517/meta.py92
-rw-r--r--src/pip/_vendor/pkg_resources/LICENSE24
-rw-r--r--src/pip/_vendor/pkg_resources/__init__.py314
-rw-r--r--src/pip/_vendor/pkg_resources/py31compat.py23
-rw-r--r--src/pip/_vendor/platformdirs/LICENSE21
-rw-r--r--src/pip/_vendor/platformdirs/LICENSE.txt22
-rw-r--r--src/pip/_vendor/platformdirs/__init__.py137
-rw-r--r--src/pip/_vendor/platformdirs/__main__.py2
-rw-r--r--src/pip/_vendor/platformdirs/android.py27
-rw-r--r--src/pip/_vendor/platformdirs/api.py9
-rw-r--r--src/pip/_vendor/platformdirs/macos.py2
-rw-r--r--src/pip/_vendor/platformdirs/unix.py9
-rw-r--r--src/pip/_vendor/platformdirs/version.py8
-rw-r--r--src/pip/_vendor/platformdirs/windows.py10
-rw-r--r--src/pip/_vendor/progress.pyi1
-rw-r--r--src/pip/_vendor/progress/LICENSE13
-rw-r--r--src/pip/_vendor/progress/__init__.py189
-rw-r--r--src/pip/_vendor/progress/bar.py93
-rw-r--r--src/pip/_vendor/progress/colors.py79
-rw-r--r--src/pip/_vendor/progress/counter.py47
-rw-r--r--src/pip/_vendor/progress/spinner.py45
-rw-r--r--src/pip/_vendor/pygments.pyi1
-rw-r--r--src/pip/_vendor/pygments/LICENSE25
-rw-r--r--src/pip/_vendor/pygments/__init__.py82
-rw-r--r--src/pip/_vendor/pygments/__main__.py17
-rw-r--r--src/pip/_vendor/pygments/cmdline.py668
-rw-r--r--src/pip/_vendor/pygments/console.py70
-rw-r--r--src/pip/_vendor/pygments/filter.py71
-rw-r--r--src/pip/_vendor/pygments/filters/__init__.py940
-rw-r--r--src/pip/_vendor/pygments/formatter.py94
-rw-r--r--src/pip/_vendor/pygments/formatters/__init__.py143
-rw-r--r--src/pip/_vendor/pygments/formatters/_mapping.py23
-rw-r--r--src/pip/_vendor/pygments/formatters/bbcode.py108
-rw-r--r--src/pip/_vendor/pygments/formatters/groff.py170
-rw-r--r--src/pip/_vendor/pygments/formatters/html.py989
-rw-r--r--src/pip/_vendor/pygments/formatters/img.py645
-rw-r--r--src/pip/_vendor/pygments/formatters/irc.py179
-rw-r--r--src/pip/_vendor/pygments/formatters/latex.py521
-rw-r--r--src/pip/_vendor/pygments/formatters/other.py161
-rw-r--r--src/pip/_vendor/pygments/formatters/pangomarkup.py83
-rw-r--r--src/pip/_vendor/pygments/formatters/rtf.py146
-rw-r--r--src/pip/_vendor/pygments/formatters/svg.py188
-rw-r--r--src/pip/_vendor/pygments/formatters/terminal.py127
-rw-r--r--src/pip/_vendor/pygments/formatters/terminal256.py338
-rw-r--r--src/pip/_vendor/pygments/lexer.py882
-rw-r--r--src/pip/_vendor/pygments/lexers/__init__.py335
-rw-r--r--src/pip/_vendor/pygments/lexers/_mapping.py541
-rw-r--r--src/pip/_vendor/pygments/lexers/python.py1204
-rw-r--r--src/pip/_vendor/pygments/modeline.py43
-rw-r--r--src/pip/_vendor/pygments/plugin.py88
-rw-r--r--src/pip/_vendor/pygments/regexopt.py91
-rw-r--r--src/pip/_vendor/pygments/scanner.py104
-rw-r--r--src/pip/_vendor/pygments/sphinxext.py155
-rw-r--r--src/pip/_vendor/pygments/style.py197
-rw-r--r--src/pip/_vendor/pygments/styles/__init__.py97
-rw-r--r--src/pip/_vendor/pygments/token.py213
-rw-r--r--src/pip/_vendor/pygments/unistring.py153
-rw-r--r--src/pip/_vendor/pygments/util.py308
-rw-r--r--src/pip/_vendor/pyparsing.py7107
-rw-r--r--src/pip/_vendor/pyparsing.pyi1
-rw-r--r--src/pip/_vendor/pyparsing/LICENSE (renamed from src/pip/_vendor/pyparsing.LICENSE)0
-rw-r--r--src/pip/_vendor/pyparsing/__init__.py331
-rw-r--r--src/pip/_vendor/pyparsing/actions.py207
-rw-r--r--src/pip/_vendor/pyparsing/common.py424
-rw-r--r--src/pip/_vendor/pyparsing/core.py5814
-rw-r--r--src/pip/_vendor/pyparsing/diagram/__init__.py642
-rw-r--r--src/pip/_vendor/pyparsing/exceptions.py267
-rw-r--r--src/pip/_vendor/pyparsing/helpers.py1088
-rw-r--r--src/pip/_vendor/pyparsing/py.typed0
-rw-r--r--src/pip/_vendor/pyparsing/results.py760
-rw-r--r--src/pip/_vendor/pyparsing/testing.py331
-rw-r--r--src/pip/_vendor/pyparsing/unicode.py352
-rw-r--r--src/pip/_vendor/pyparsing/util.py235
-rw-r--r--src/pip/_vendor/pyproject_hooks.pyi1
-rw-r--r--src/pip/_vendor/pyproject_hooks/LICENSE (renamed from src/pip/_vendor/pep517/LICENSE)0
-rw-r--r--src/pip/_vendor/pyproject_hooks/__init__.py23
-rw-r--r--src/pip/_vendor/pyproject_hooks/_compat.py8
-rw-r--r--src/pip/_vendor/pyproject_hooks/_impl.py (renamed from src/pip/_vendor/pep517/wrappers.py)245
-rw-r--r--src/pip/_vendor/pyproject_hooks/_in_process/__init__.py (renamed from src/pip/_vendor/pep517/in_process/__init__.py)15
-rw-r--r--src/pip/_vendor/pyproject_hooks/_in_process/_in_process.py (renamed from src/pip/_vendor/pep517/in_process/_in_process.py)44
-rw-r--r--src/pip/_vendor/requests/__init__.py98
-rw-r--r--src/pip/_vendor/requests/__version__.py20
-rw-r--r--src/pip/_vendor/requests/_internal_utils.py24
-rw-r--r--src/pip/_vendor/requests/adapters.py229
-rw-r--r--src/pip/_vendor/requests/api.py18
-rw-r--r--src/pip/_vendor/requests/auth.py156
-rw-r--r--src/pip/_vendor/requests/certs.py12
-rw-r--r--src/pip/_vendor/requests/compat.py89
-rw-r--r--src/pip/_vendor/requests/cookies.py152
-rw-r--r--src/pip/_vendor/requests/exceptions.py32
-rw-r--r--src/pip/_vendor/requests/help.py101
-rw-r--r--src/pip/_vendor/requests/hooks.py7
-rw-r--r--src/pip/_vendor/requests/models.py360
-rw-r--r--src/pip/_vendor/requests/sessions.py288
-rw-r--r--src/pip/_vendor/requests/status_codes.py169
-rw-r--r--src/pip/_vendor/requests/structures.py12
-rw-r--r--src/pip/_vendor/requests/utils.py439
-rw-r--r--src/pip/_vendor/resolvelib/__init__.py2
-rw-r--r--src/pip/_vendor/resolvelib/compat/collections_abc.pyi1
-rw-r--r--src/pip/_vendor/resolvelib/providers.py14
-rw-r--r--src/pip/_vendor/resolvelib/providers.pyi4
-rw-r--r--src/pip/_vendor/resolvelib/reporters.py2
-rw-r--r--src/pip/_vendor/resolvelib/reporters.pyi2
-rw-r--r--src/pip/_vendor/resolvelib/resolvers.py44
-rw-r--r--src/pip/_vendor/resolvelib/resolvers.pyi12
-rw-r--r--src/pip/_vendor/resolvelib/structs.py11
-rw-r--r--src/pip/_vendor/resolvelib/structs.pyi2
-rw-r--r--src/pip/_vendor/rich/LICENSE19
-rw-r--r--src/pip/_vendor/rich/__init__.py177
-rw-r--r--src/pip/_vendor/rich/__main__.py274
-rw-r--r--src/pip/_vendor/rich/_cell_widths.py451
-rw-r--r--src/pip/_vendor/rich/_emoji_codes.py3610
-rw-r--r--src/pip/_vendor/rich/_emoji_replace.py32
-rw-r--r--src/pip/_vendor/rich/_export_format.py78
-rw-r--r--src/pip/_vendor/rich/_extension.py10
-rw-r--r--src/pip/_vendor/rich/_inspect.py270
-rw-r--r--src/pip/_vendor/rich/_log_render.py94
-rw-r--r--src/pip/_vendor/rich/_loop.py43
-rw-r--r--src/pip/_vendor/rich/_null_file.py83
-rw-r--r--src/pip/_vendor/rich/_palettes.py309
-rw-r--r--src/pip/_vendor/rich/_pick.py17
-rw-r--r--src/pip/_vendor/rich/_ratio.py160
-rw-r--r--src/pip/_vendor/rich/_spinners.py482
-rw-r--r--src/pip/_vendor/rich/_stack.py16
-rw-r--r--src/pip/_vendor/rich/_timer.py19
-rw-r--r--src/pip/_vendor/rich/_win32_console.py662
-rw-r--r--src/pip/_vendor/rich/_windows.py72
-rw-r--r--src/pip/_vendor/rich/_windows_renderer.py56
-rw-r--r--src/pip/_vendor/rich/_wrap.py56
-rw-r--r--src/pip/_vendor/rich/abc.py33
-rw-r--r--src/pip/_vendor/rich/align.py311
-rw-r--r--src/pip/_vendor/rich/ansi.py237
-rw-r--r--src/pip/_vendor/rich/bar.py94
-rw-r--r--src/pip/_vendor/rich/box.py517
-rw-r--r--src/pip/_vendor/rich/cells.py154
-rw-r--r--src/pip/_vendor/rich/color.py618
-rw-r--r--src/pip/_vendor/rich/color_triplet.py38
-rw-r--r--src/pip/_vendor/rich/columns.py187
-rw-r--r--src/pip/_vendor/rich/console.py2612
-rw-r--r--src/pip/_vendor/rich/constrain.py37
-rw-r--r--src/pip/_vendor/rich/containers.py167
-rw-r--r--src/pip/_vendor/rich/control.py225
-rw-r--r--src/pip/_vendor/rich/default_styles.py188
-rw-r--r--src/pip/_vendor/rich/diagnose.py37
-rw-r--r--src/pip/_vendor/rich/emoji.py96
-rw-r--r--src/pip/_vendor/rich/errors.py34
-rw-r--r--src/pip/_vendor/rich/file_proxy.py54
-rw-r--r--src/pip/_vendor/rich/filesize.py89
-rw-r--r--src/pip/_vendor/rich/highlighter.py232
-rw-r--r--src/pip/_vendor/rich/json.py140
-rw-r--r--src/pip/_vendor/rich/jupyter.py101
-rw-r--r--src/pip/_vendor/rich/layout.py443
-rw-r--r--src/pip/_vendor/rich/live.py373
-rw-r--r--src/pip/_vendor/rich/live_render.py113
-rw-r--r--src/pip/_vendor/rich/logging.py289
-rw-r--r--src/pip/_vendor/rich/markup.py246
-rw-r--r--src/pip/_vendor/rich/measure.py151
-rw-r--r--src/pip/_vendor/rich/padding.py141
-rw-r--r--src/pip/_vendor/rich/pager.py34
-rw-r--r--src/pip/_vendor/rich/palette.py100
-rw-r--r--src/pip/_vendor/rich/panel.py308
-rw-r--r--src/pip/_vendor/rich/pretty.py1029
-rw-r--r--src/pip/_vendor/rich/progress.py1707
-rw-r--r--src/pip/_vendor/rich/progress_bar.py224
-rw-r--r--src/pip/_vendor/rich/prompt.py376
-rw-r--r--src/pip/_vendor/rich/protocol.py42
-rw-r--r--src/pip/_vendor/rich/py.typed0
-rw-r--r--src/pip/_vendor/rich/region.py10
-rw-r--r--src/pip/_vendor/rich/repr.py149
-rw-r--r--src/pip/_vendor/rich/rule.py134
-rw-r--r--src/pip/_vendor/rich/scope.py86
-rw-r--r--src/pip/_vendor/rich/screen.py54
-rw-r--r--src/pip/_vendor/rich/segment.py739
-rw-r--r--src/pip/_vendor/rich/spinner.py136
-rw-r--r--src/pip/_vendor/rich/status.py132
-rw-r--r--src/pip/_vendor/rich/style.py773
-rw-r--r--src/pip/_vendor/rich/styled.py42
-rw-r--r--src/pip/_vendor/rich/syntax.py945
-rw-r--r--src/pip/_vendor/rich/table.py1002
-rw-r--r--src/pip/_vendor/rich/terminal_theme.py153
-rw-r--r--src/pip/_vendor/rich/text.py1311
-rw-r--r--src/pip/_vendor/rich/theme.py112
-rw-r--r--src/pip/_vendor/rich/themes.py5
-rw-r--r--src/pip/_vendor/rich/traceback.py677
-rw-r--r--src/pip/_vendor/rich/tree.py251
-rw-r--r--src/pip/_vendor/tenacity/__init__.py2
-rw-r--r--src/pip/_vendor/tenacity/retry.py27
-rw-r--r--src/pip/_vendor/tenacity/wait.py71
-rw-r--r--src/pip/_vendor/tomli/__init__.py11
-rw-r--r--src/pip/_vendor/tomli/_parser.py360
-rw-r--r--src/pip/_vendor/tomli/_re.py94
-rw-r--r--src/pip/_vendor/tomli/_types.py10
-rw-r--r--src/pip/_vendor/typing_extensions.LICENSE254
-rw-r--r--src/pip/_vendor/typing_extensions.py2209
-rw-r--r--src/pip/_vendor/typing_extensions.pyi1
-rw-r--r--src/pip/_vendor/urllib3/__init__.py17
-rw-r--r--src/pip/_vendor/urllib3/_version.py2
-rw-r--r--src/pip/_vendor/urllib3/connection.py10
-rw-r--r--src/pip/_vendor/urllib3/connectionpool.py42
-rw-r--r--src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py2
-rw-r--r--src/pip/_vendor/urllib3/contrib/appengine.py2
-rw-r--r--src/pip/_vendor/urllib3/contrib/ntlmpool.py4
-rw-r--r--src/pip/_vendor/urllib3/contrib/pyopenssl.py17
-rw-r--r--src/pip/_vendor/urllib3/contrib/securetransport.py1
-rw-r--r--src/pip/_vendor/urllib3/packages/__init__.py5
-rw-r--r--src/pip/_vendor/urllib3/packages/six.py1
-rw-r--r--src/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py24
-rw-r--r--src/pip/_vendor/urllib3/poolmanager.py1
-rw-r--r--src/pip/_vendor/urllib3/response.py72
-rw-r--r--src/pip/_vendor/urllib3/util/connection.py3
-rw-r--r--src/pip/_vendor/urllib3/util/request.py6
-rw-r--r--src/pip/_vendor/urllib3/util/retry.py26
-rw-r--r--src/pip/_vendor/urllib3/util/ssl_match_hostname.py (renamed from src/pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py)15
-rw-r--r--src/pip/_vendor/urllib3/util/ssltransport.py4
-rw-r--r--src/pip/_vendor/urllib3/util/url.py7
-rw-r--r--src/pip/_vendor/urllib3/util/wait.py1
-rw-r--r--src/pip/_vendor/vendor.txt41
495 files changed, 68604 insertions, 37795 deletions
diff --git a/src/pip/__init__.py b/src/pip/__init__.py
index 09e946fbe..ce90d06bf 100644
--- a/src/pip/__init__.py
+++ b/src/pip/__init__.py
@@ -1,6 +1,6 @@
from typing import List, Optional
-__version__ = "22.0.dev0"
+__version__ = "23.1.dev0"
def main(args: Optional[List[str]] = None) -> int:
diff --git a/src/pip/__pip-runner__.py b/src/pip/__pip-runner__.py
new file mode 100644
index 000000000..49a148a09
--- /dev/null
+++ b/src/pip/__pip-runner__.py
@@ -0,0 +1,50 @@
+"""Execute exactly this copy of pip, within a different environment.
+
+This file is named as it is, to ensure that this module can't be imported via
+an import statement.
+"""
+
+# /!\ This version compatibility check section must be Python 2 compatible. /!\
+
+import sys
+
+# Copied from setup.py
+PYTHON_REQUIRES = (3, 7)
+
+
+def version_str(version): # type: ignore
+ return ".".join(str(v) for v in version)
+
+
+if sys.version_info[:2] < PYTHON_REQUIRES:
+ raise SystemExit(
+ "This version of pip does not support python {} (requires >={}).".format(
+ version_str(sys.version_info[:2]), version_str(PYTHON_REQUIRES)
+ )
+ )
+
+# From here on, we can use Python 3 features, but the syntax must remain
+# Python 2 compatible.
+
+import runpy # noqa: E402
+from importlib.machinery import PathFinder # noqa: E402
+from os.path import dirname # noqa: E402
+
+PIP_SOURCES_ROOT = dirname(dirname(__file__))
+
+
+class PipImportRedirectingFinder:
+ @classmethod
+ def find_spec(self, fullname, path=None, target=None): # type: ignore
+ if fullname != "pip":
+ return None
+
+ spec = PathFinder.find_spec(fullname, [PIP_SOURCES_ROOT], target)
+ assert spec, (PIP_SOURCES_ROOT, fullname)
+ return spec
+
+
+sys.meta_path.insert(0, PipImportRedirectingFinder())
+
+assert __name__ == "__main__", "Cannot run __pip-runner__.py as a non-main module"
+runpy.run_module("pip", run_name="__main__", alter_sys=True)
diff --git a/src/pip/_internal/build_env.py b/src/pip/_internal/build_env.py
index a30b45826..4f704a354 100644
--- a/src/pip/_internal/build_env.py
+++ b/src/pip/_internal/build_env.py
@@ -1,17 +1,15 @@
"""Build Environment used for isolation during sdist building
"""
-import contextlib
import logging
import os
import pathlib
+import site
import sys
import textwrap
-import zipfile
from collections import OrderedDict
-from sysconfig import get_paths
from types import TracebackType
-from typing import TYPE_CHECKING, Iterable, Iterator, List, Optional, Set, Tuple, Type
+from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type, Union
from pip._vendor.certifi import where
from pip._vendor.packaging.requirements import Requirement
@@ -19,8 +17,8 @@ from pip._vendor.packaging.version import Version
from pip import __file__ as pip_location
from pip._internal.cli.spinners import open_spinner
-from pip._internal.locations import get_platlib, get_prefixed_libs, get_purelib
-from pip._internal.metadata import get_environment
+from pip._internal.locations import get_platlib, get_purelib, get_scheme
+from pip._internal.metadata import get_default_environment, get_environment
from pip._internal.utils.subprocess import call_subprocess
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
@@ -30,41 +28,53 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
+def _dedup(a: str, b: str) -> Union[Tuple[str], Tuple[str, str]]:
+ return (a, b) if a != b else (a,)
+
+
class _Prefix:
def __init__(self, path: str) -> None:
self.path = path
self.setup = False
- self.bin_dir = get_paths(
- "nt" if os.name == "nt" else "posix_prefix",
- vars={"base": path, "platbase": path},
- )["scripts"]
- self.lib_dirs = get_prefixed_libs(path)
+ scheme = get_scheme("", prefix=path)
+ self.bin_dir = scheme.scripts
+ self.lib_dirs = _dedup(scheme.purelib, scheme.platlib)
-@contextlib.contextmanager
-def _create_standalone_pip() -> Iterator[str]:
- """Create a "standalone pip" zip file.
+def get_runnable_pip() -> str:
+ """Get a file to pass to a Python executable, to run the currently-running pip.
- The zip file's content is identical to the currently-running pip.
- It will be used to install requirements into the build environment.
+ This is used to run a pip subprocess, for installing requirements into the build
+ environment.
"""
source = pathlib.Path(pip_location).resolve().parent
- # Return the current instance if `source` is not a directory. We can't build
- # a zip from this, and it likely means the instance is already standalone.
if not source.is_dir():
- yield str(source)
- return
+ # This would happen if someone is using pip from inside a zip file. In that
+ # case, we can use that directly.
+ return str(source)
+
+ return os.fsdecode(source / "__pip-runner__.py")
+
- with TempDirectory(kind="standalone-pip") as tmp_dir:
- pip_zip = os.path.join(tmp_dir.path, "__env_pip__.zip")
- kwargs = {}
- if sys.version_info >= (3, 8):
- kwargs["strict_timestamps"] = False
- with zipfile.ZipFile(pip_zip, "w", **kwargs) as zf:
- for child in source.rglob("*"):
- zf.write(child, child.relative_to(source.parent).as_posix())
- yield os.path.join(pip_zip, "pip")
+def _get_system_sitepackages() -> Set[str]:
+ """Get system site packages
+
+ Usually from site.getsitepackages,
+ but fallback on `get_purelib()/get_platlib()` if unavailable
+ (e.g. in a virtualenv created by virtualenv<20)
+
+ Returns normalized set of strings.
+ """
+ if hasattr(site, "getsitepackages"):
+ system_sites = site.getsitepackages()
+ else:
+ # virtualenv < 20 overwrites site.py without getsitepackages
+ # fallback on get_purelib/get_platlib.
+ # this is known to miss things, but shouldn't in the cases
+ # where getsitepackages() has been removed (inside a virtualenv)
+ system_sites = [get_purelib(), get_platlib()]
+ return {os.path.normcase(path) for path in system_sites}
class BuildEnvironment:
@@ -87,9 +97,8 @@ class BuildEnvironment:
# Customize site to:
# - ensure .pth files are honored
# - prevent access to system site packages
- system_sites = {
- os.path.normcase(site) for site in (get_purelib(), get_platlib())
- }
+ system_sites = _get_system_sitepackages()
+
self._site_dir = os.path.join(temp_dir.path, "site")
if not os.path.exists(self._site_dir):
os.mkdir(self._site_dir)
@@ -168,9 +177,17 @@ class BuildEnvironment:
missing = set()
conflicting = set()
if reqs:
- env = get_environment(self._lib_dirs)
+ env = (
+ get_environment(self._lib_dirs)
+ if hasattr(self, "_lib_dirs")
+ else get_default_environment()
+ )
for req_str in reqs:
req = Requirement(req_str)
+ # We're explicitly evaluating with an empty extra value, since build
+ # environments are not provided any mechanism to select specific extras.
+ if req.marker is not None and not req.marker.evaluate({"extra": ""}):
+ continue
dist = env.get_distribution(req.name)
if not dist:
missing.add(req_str)
@@ -179,7 +196,7 @@ class BuildEnvironment:
installed_req_str = f"{req.name}=={dist.version}"
else:
installed_req_str = f"{req.name}==={dist.version}"
- if dist.version not in req.specifier:
+ if not req.specifier.contains(dist.version, prereleases=True):
conflicting.add((installed_req_str, req_str))
# FIXME: Consider direct URL?
return conflicting, missing
@@ -189,29 +206,21 @@ class BuildEnvironment:
finder: "PackageFinder",
requirements: Iterable[str],
prefix_as_string: str,
- message: str,
+ *,
+ kind: str,
) -> None:
prefix = self._prefixes[prefix_as_string]
assert not prefix.setup
prefix.setup = True
if not requirements:
return
- with contextlib.ExitStack() as ctx:
- # TODO: Remove this block when dropping 3.6 support. Python 3.6
- # lacks importlib.resources and pep517 has issues loading files in
- # a zip, so we fallback to the "old" method by adding the current
- # pip directory to the child process's sys.path.
- if sys.version_info < (3, 7):
- pip_runnable = os.path.dirname(pip_location)
- else:
- pip_runnable = ctx.enter_context(_create_standalone_pip())
- self._install_requirements(
- pip_runnable,
- finder,
- requirements,
- prefix,
- message,
- )
+ self._install_requirements(
+ get_runnable_pip(),
+ finder,
+ requirements,
+ prefix,
+ kind=kind,
+ )
@staticmethod
def _install_requirements(
@@ -219,7 +228,8 @@ class BuildEnvironment:
finder: "PackageFinder",
requirements: Iterable[str],
prefix: _Prefix,
- message: str,
+ *,
+ kind: str,
) -> None:
args: List[str] = [
sys.executable,
@@ -261,8 +271,13 @@ class BuildEnvironment:
args.append("--")
args.extend(requirements)
extra_environ = {"_PIP_STANDALONE_CERT": where()}
- with open_spinner(message) as spinner:
- call_subprocess(args, spinner=spinner, extra_environ=extra_environ)
+ with open_spinner(f"Installing {kind}") as spinner:
+ call_subprocess(
+ args,
+ command_desc=f"pip subprocess to install {kind}",
+ spinner=spinner,
+ extra_environ=extra_environ,
+ )
class NoOpBuildEnvironment(BuildEnvironment):
@@ -290,6 +305,7 @@ class NoOpBuildEnvironment(BuildEnvironment):
finder: "PackageFinder",
requirements: Iterable[str],
prefix_as_string: str,
- message: str,
+ *,
+ kind: str,
) -> None:
raise NotImplementedError()
diff --git a/src/pip/_internal/cache.py b/src/pip/_internal/cache.py
index 1d6df2201..c53b7f023 100644
--- a/src/pip/_internal/cache.py
+++ b/src/pip/_internal/cache.py
@@ -5,12 +5,14 @@ import hashlib
import json
import logging
import os
+from pathlib import Path
from typing import Any, Dict, List, Optional, Set
from pip._vendor.packaging.tags import Tag, interpreter_name, interpreter_version
from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.exceptions import InvalidWheelFilename
+from pip._internal.models.direct_url import DirectUrl
from pip._internal.models.format_control import FormatControl
from pip._internal.models.link import Link
from pip._internal.models.wheel import Wheel
@@ -19,6 +21,8 @@ from pip._internal.utils.urls import path_to_url
logger = logging.getLogger(__name__)
+ORIGIN_JSON_NAME = "origin.json"
+
def _hash_dict(d: Dict[str, str]) -> str:
"""Return a stable sha224 of a dictionary."""
@@ -204,6 +208,10 @@ class CacheEntry:
):
self.link = link
self.persistent = persistent
+ self.origin: Optional[DirectUrl] = None
+ origin_direct_url_path = Path(self.link.file_path).parent / ORIGIN_JSON_NAME
+ if origin_direct_url_path.exists():
+ self.origin = DirectUrl.from_json(origin_direct_url_path.read_text())
class WheelCache(Cache):
@@ -213,7 +221,11 @@ class WheelCache(Cache):
when a certain link is not found in the simple wheel cache first.
"""
- def __init__(self, cache_dir: str, format_control: FormatControl) -> None:
+ def __init__(
+ self, cache_dir: str, format_control: Optional[FormatControl] = None
+ ) -> None:
+ if format_control is None:
+ format_control = FormatControl()
super().__init__(cache_dir, format_control, {"binary"})
self._wheel_cache = SimpleWheelCache(cache_dir, format_control)
self._ephem_cache = EphemWheelCache(format_control)
@@ -262,3 +274,20 @@ class WheelCache(Cache):
return CacheEntry(retval, persistent=False)
return None
+
+ @staticmethod
+ def record_download_origin(cache_dir: str, download_info: DirectUrl) -> None:
+ origin_path = Path(cache_dir) / ORIGIN_JSON_NAME
+ if origin_path.is_file():
+ origin = DirectUrl.from_json(origin_path.read_text())
+ # TODO: use DirectUrl.equivalent when https://github.com/pypa/pip/pull/10564
+ # is merged.
+ if origin.url != download_info.url:
+ logger.warning(
+ "Origin URL %s in cache entry %s does not match download URL %s. "
+ "This is likely a pip bug or a cache corruption issue.",
+ origin.url,
+ cache_dir,
+ download_info.url,
+ )
+ origin_path.write_text(download_info.to_json(), encoding="utf-8")
diff --git a/src/pip/_internal/cli/autocompletion.py b/src/pip/_internal/cli/autocompletion.py
index 3cad14860..226fe84dc 100644
--- a/src/pip/_internal/cli/autocompletion.py
+++ b/src/pip/_internal/cli/autocompletion.py
@@ -59,6 +59,14 @@ def autocomplete() -> None:
print(dist)
sys.exit(1)
+ should_list_installables = (
+ not current.startswith("-") and subcommand_name == "install"
+ )
+ if should_list_installables:
+ for path in auto_complete_paths(current, "path"):
+ print(path)
+ sys.exit(1)
+
subcommand = create_command(subcommand_name)
for opt in subcommand.parser.option_list_all:
@@ -138,7 +146,7 @@ def auto_complete_paths(current: str, completion_type: str) -> Iterable[str]:
starting with ``current``.
:param current: The word to be completed
- :param completion_type: path completion type(`file`, `path` or `dir`)i
+ :param completion_type: path completion type(``file``, ``path`` or ``dir``)
:return: A generator of regular files and/or directories
"""
directory, filename = os.path.split(current)
diff --git a/src/pip/_internal/cli/base_command.py b/src/pip/_internal/cli/base_command.py
index 0afe7e75d..5bd7e67e6 100644
--- a/src/pip/_internal/cli/base_command.py
+++ b/src/pip/_internal/cli/base_command.py
@@ -10,6 +10,8 @@ import traceback
from optparse import Values
from typing import Any, Callable, List, Optional, Tuple
+from pip._vendor.rich import traceback as rich_traceback
+
from pip._internal.cli import cmdoptions
from pip._internal.cli.command_context import CommandContextMixIn
from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
@@ -22,6 +24,7 @@ from pip._internal.cli.status_codes import (
from pip._internal.exceptions import (
BadCommand,
CommandError,
+ DiagnosticPipError,
InstallationError,
NetworkConnectionError,
PreviousBuildDirError,
@@ -148,13 +151,6 @@ class Command(CommandContextMixIn):
)
options.cache_dir = None
- if "2020-resolver" in options.features_enabled:
- logger.warning(
- "--use-feature=2020-resolver no longer has any effect, "
- "since it is now the default dependency resolver in pip. "
- "This will become an error in pip 21.0."
- )
-
def intercepts_unhandled_exc(
run_func: Callable[..., int]
) -> Callable[..., int]:
@@ -164,6 +160,11 @@ class Command(CommandContextMixIn):
status = run_func(*args)
assert isinstance(status, int)
return status
+ except DiagnosticPipError as exc:
+ logger.error("[present-rich] %s", exc)
+ logger.debug("Exception information:", exc_info=True)
+
+ return ERROR
except PreviousBuildDirError as exc:
logger.critical(str(exc))
logger.debug("Exception information:", exc_info=True)
@@ -209,6 +210,7 @@ class Command(CommandContextMixIn):
run = intercepts_unhandled_exc(self.run)
else:
run = self.run
+ rich_traceback.install(show_locals=True)
return run(options, args)
finally:
self.handle_pip_version_check(options)
diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py
index 11ddc610c..2bbff2d4d 100644
--- a/src/pip/_internal/cli/cmdoptions.py
+++ b/src/pip/_internal/cli/cmdoptions.py
@@ -10,9 +10,10 @@ pass on state. To be consistent, all options will follow this design.
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
+import importlib.util
+import logging
import os
import textwrap
-import warnings
from functools import partial
from optparse import SUPPRESS_HELP, Option, OptionGroup, OptionParser, Values
from textwrap import dedent
@@ -21,7 +22,6 @@ from typing import Any, Callable, Dict, Optional, Tuple
from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.cli.parser import ConfigOptionParser
-from pip._internal.cli.progress_bars import BAR_TYPES
from pip._internal.exceptions import CommandError
from pip._internal.locations import USER_CACHE_DIR, get_src_prefix
from pip._internal.models.format_control import FormatControl
@@ -30,6 +30,8 @@ from pip._internal.models.target_python import TargetPython
from pip._internal.utils.hashes import STRONG_HASHES
from pip._internal.utils.misc import strtobool
+logger = logging.getLogger(__name__)
+
def raise_option_error(parser: OptionParser, option: Option, msg: str) -> None:
"""
@@ -57,32 +59,6 @@ def make_option_group(group: Dict[str, Any], parser: ConfigOptionParser) -> Opti
return option_group
-def check_install_build_global(
- options: Values, check_options: Optional[Values] = None
-) -> None:
- """Disable wheels if per-setup.py call options are set.
-
- :param options: The OptionParser options to update.
- :param check_options: The options to check, if not supplied defaults to
- options.
- """
- if check_options is None:
- check_options = options
-
- def getname(n: str) -> Optional[Any]:
- return getattr(check_options, n, None)
-
- names = ["build_options", "global_options", "install_options"]
- if any(map(getname, names)):
- control = options.format_control
- control.disallow_binaries()
- warnings.warn(
- "Disabling all use of wheels due to the use of --build-option "
- "/ --global-option / --install-option.",
- stacklevel=2,
- )
-
-
def check_dist_restriction(options: Values, check_target: bool = False) -> None:
"""Function for determining if custom platform options are allowed.
@@ -188,6 +164,21 @@ require_virtualenv: Callable[..., Option] = partial(
),
)
+override_externally_managed: Callable[..., Option] = partial(
+ Option,
+ "--break-system-packages",
+ dest="override_externally_managed",
+ action="store_true",
+ help="Allow pip to modify an EXTERNALLY-MANAGED Python installation",
+)
+
+python: Callable[..., Option] = partial(
+ Option,
+ "--python",
+ dest="python",
+ help="Run pip with the specified Python interpreter.",
+)
+
verbose: Callable[..., Option] = partial(
Option,
"-v",
@@ -235,13 +226,9 @@ progress_bar: Callable[..., Option] = partial(
"--progress-bar",
dest="progress_bar",
type="choice",
- choices=list(BAR_TYPES.keys()),
+ choices=["on", "off"],
default="on",
- help=(
- "Specify type of progress to be displayed ["
- + "|".join(BAR_TYPES.keys())
- + "] (default: %default)"
- ),
+ help="Specify whether the progress bar should be used [on, off] (default: on)",
)
log: Callable[..., Option] = partial(
@@ -265,13 +252,26 @@ no_input: Callable[..., Option] = partial(
help="Disable prompting for input.",
)
+keyring_provider: Callable[..., Option] = partial(
+ Option,
+ "--keyring-provider",
+ dest="keyring_provider",
+ choices=["auto", "disabled", "import", "subprocess"],
+ default="disabled",
+ help=(
+ "Enable the credential lookup via the keyring library if user input is allowed."
+ " Specify which mechanism to use [disabled, import, subprocess]."
+ " (default: disabled)"
+ ),
+)
+
proxy: Callable[..., Option] = partial(
Option,
"--proxy",
dest="proxy",
type="str",
default="",
- help="Specify a proxy in the form [user:passwd@]proxy.server:port.",
+ help="Specify a proxy in the form scheme://[user:passwd@]proxy.server:port.",
)
retries: Callable[..., Option] = partial(
@@ -752,6 +752,15 @@ no_build_isolation: Callable[..., Option] = partial(
"if this option is used.",
)
+check_build_deps: Callable[..., Option] = partial(
+ Option,
+ "--check-build-dependencies",
+ dest="check_build_deps",
+ action="store_true",
+ default=False,
+ help="Check the build dependencies when PEP517 is used.",
+)
+
def _handle_no_use_pep517(
option: Option, opt: str, value: str, parser: OptionParser
@@ -774,6 +783,12 @@ def _handle_no_use_pep517(
"""
raise_option_error(parser, option=option, msg=msg)
+ # If user doesn't wish to use pep517, we check if setuptools is installed
+ # and raise error if it is not.
+ if not importlib.util.find_spec("setuptools"):
+ msg = "It is not possible to use --no-use-pep517 without setuptools installed."
+ raise_option_error(parser, option=option, msg=msg)
+
# Otherwise, --no-use-pep517 was passed via the command-line.
parser.values.use_pep517 = False
@@ -798,17 +813,38 @@ no_use_pep517: Any = partial(
help=SUPPRESS_HELP,
)
-install_options: Callable[..., Option] = partial(
+
+def _handle_config_settings(
+ option: Option, opt_str: str, value: str, parser: OptionParser
+) -> None:
+ key, sep, val = value.partition("=")
+ if sep != "=":
+ parser.error(f"Arguments to {opt_str} must be of the form KEY=VAL") # noqa
+ dest = getattr(parser.values, option.dest)
+ if dest is None:
+ dest = {}
+ setattr(parser.values, option.dest, dest)
+ if key in dest:
+ if isinstance(dest[key], list):
+ dest[key].append(val)
+ else:
+ dest[key] = [dest[key], val]
+ else:
+ dest[key] = val
+
+
+config_settings: Callable[..., Option] = partial(
Option,
- "--install-option",
- dest="install_options",
- action="append",
- metavar="options",
- help="Extra arguments to be supplied to the setup.py install "
- 'command (use like --install-option="--install-scripts=/usr/local/'
- 'bin"). Use multiple --install-option options to pass multiple '
- "options to setup.py install. If you are using an option with a "
- "directory path, be sure to use absolute path.",
+ "-C",
+ "--config-settings",
+ dest="config_settings",
+ type=str,
+ action="callback",
+ callback=_handle_config_settings,
+ metavar="settings",
+ help="Configuration settings to be passed to the PEP 517 build backend. "
+ "Settings take the form KEY=VALUE. Use multiple --config-settings options "
+ "to pass multiple keys to the backend.",
)
build_options: Callable[..., Option] = partial(
@@ -857,6 +893,15 @@ disable_pip_version_check: Callable[..., Option] = partial(
"of pip is available for download. Implied with --no-index.",
)
+root_user_action: Callable[..., Option] = partial(
+ Option,
+ "--root-user-action",
+ dest="root_user_action",
+ default="warn",
+ choices=["warn", "ignore"],
+ help="Action if pip is run as a root user. By default, a warning message is shown.",
+)
+
def _handle_merge_hash(
option: Option, opt_str: str, value: str, parser: OptionParser
@@ -952,7 +997,11 @@ use_new_feature: Callable[..., Option] = partial(
metavar="feature",
action="append",
default=[],
- choices=["2020-resolver", "fast-deps", "in-tree-build"],
+ choices=[
+ "fast-deps",
+ "truststore",
+ "no-binary-enable-wheel-cache",
+ ],
help="Enable new functionality, that may be backward incompatible.",
)
@@ -963,7 +1012,9 @@ use_deprecated_feature: Callable[..., Option] = partial(
metavar="feature",
action="append",
default=[],
- choices=["legacy-resolver", "out-of-tree-build"],
+ choices=[
+ "legacy-resolver",
+ ],
help=("Enable deprecated functionality, that will be removed in the future."),
)
@@ -979,11 +1030,13 @@ general_group: Dict[str, Any] = {
debug_mode,
isolated_mode,
require_virtualenv,
+ python,
verbose,
version,
quiet,
log,
no_input,
+ keyring_provider,
proxy,
retries,
timeout,
diff --git a/src/pip/_internal/cli/command_context.py b/src/pip/_internal/cli/command_context.py
index ed6832237..139995ac3 100644
--- a/src/pip/_internal/cli/command_context.py
+++ b/src/pip/_internal/cli/command_context.py
@@ -1,5 +1,5 @@
from contextlib import ExitStack, contextmanager
-from typing import ContextManager, Iterator, TypeVar
+from typing import ContextManager, Generator, TypeVar
_T = TypeVar("_T", covariant=True)
@@ -11,7 +11,7 @@ class CommandContextMixIn:
self._main_context = ExitStack()
@contextmanager
- def main_context(self) -> Iterator[None]:
+ def main_context(self) -> Generator[None, None, None]:
assert not self._in_main_context
self._in_main_context = True
diff --git a/src/pip/_internal/cli/main_parser.py b/src/pip/_internal/cli/main_parser.py
index 3666ab04c..5ade356b9 100644
--- a/src/pip/_internal/cli/main_parser.py
+++ b/src/pip/_internal/cli/main_parser.py
@@ -2,9 +2,11 @@
"""
import os
+import subprocess
import sys
-from typing import List, Tuple
+from typing import List, Optional, Tuple
+from pip._internal.build_env import get_runnable_pip
from pip._internal.cli import cmdoptions
from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
from pip._internal.commands import commands_dict, get_similar_commands
@@ -45,6 +47,25 @@ def create_main_parser() -> ConfigOptionParser:
return parser
+def identify_python_interpreter(python: str) -> Optional[str]:
+ # If the named file exists, use it.
+ # If it's a directory, assume it's a virtual environment and
+ # look for the environment's Python executable.
+ if os.path.exists(python):
+ if os.path.isdir(python):
+ # bin/python for Unix, Scripts/python.exe for Windows
+ # Try both in case of odd cases like cygwin.
+ for exe in ("bin/python", "Scripts/python.exe"):
+ py = os.path.join(python, exe)
+ if os.path.exists(py):
+ return py
+ else:
+ return python
+
+ # Could not find the interpreter specified
+ return None
+
+
def parse_command(args: List[str]) -> Tuple[str, List[str]]:
parser = create_main_parser()
@@ -57,6 +78,32 @@ def parse_command(args: List[str]) -> Tuple[str, List[str]]:
# args_else: ['install', '--user', 'INITools']
general_options, args_else = parser.parse_args(args)
+ # --python
+ if general_options.python and "_PIP_RUNNING_IN_SUBPROCESS" not in os.environ:
+ # Re-invoke pip using the specified Python interpreter
+ interpreter = identify_python_interpreter(general_options.python)
+ if interpreter is None:
+ raise CommandError(
+ f"Could not locate Python interpreter {general_options.python}"
+ )
+
+ pip_cmd = [
+ interpreter,
+ get_runnable_pip(),
+ ]
+ pip_cmd.extend(args)
+
+ # Set a flag so the child doesn't re-invoke itself, causing
+ # an infinite loop.
+ os.environ["_PIP_RUNNING_IN_SUBPROCESS"] = "1"
+ returncode = 0
+ try:
+ proc = subprocess.run(pip_cmd)
+ returncode = proc.returncode
+ except (subprocess.SubprocessError, OSError) as exc:
+ raise CommandError(f"Failed to run pip under {interpreter}: {exc}")
+ sys.exit(returncode)
+
# --version
if general_options.version:
sys.stdout.write(parser.version)
diff --git a/src/pip/_internal/cli/parser.py b/src/pip/_internal/cli/parser.py
index a1c99a8cb..c762cf278 100644
--- a/src/pip/_internal/cli/parser.py
+++ b/src/pip/_internal/cli/parser.py
@@ -6,7 +6,7 @@ import shutil
import sys
import textwrap
from contextlib import suppress
-from typing import Any, Dict, Iterator, List, Tuple
+from typing import Any, Dict, Generator, List, Tuple
from pip._internal.cli.status_codes import UNKNOWN_ERROR
from pip._internal.configuration import Configuration, ConfigurationError
@@ -175,7 +175,9 @@ class ConfigOptionParser(CustomOptionParser):
print(f"An error occurred during configuration: {exc}")
sys.exit(3)
- def _get_ordered_configuration_items(self) -> Iterator[Tuple[str, Any]]:
+ def _get_ordered_configuration_items(
+ self,
+ ) -> Generator[Tuple[str, Any], None, None]:
# Configuration gives keys in an unordered manner. Order them.
override_order = ["global", self.name, ":env:"]
diff --git a/src/pip/_internal/cli/progress_bars.py b/src/pip/_internal/cli/progress_bars.py
index f3db29519..0ad14031c 100644
--- a/src/pip/_internal/cli/progress_bars.py
+++ b/src/pip/_internal/cli/progress_bars.py
@@ -1,250 +1,68 @@
-import itertools
-import sys
-from signal import SIGINT, default_int_handler, signal
-from typing import Any
+import functools
+from typing import Callable, Generator, Iterable, Iterator, Optional, Tuple
+
+from pip._vendor.rich.progress import (
+ BarColumn,
+ DownloadColumn,
+ FileSizeColumn,
+ Progress,
+ ProgressColumn,
+ SpinnerColumn,
+ TextColumn,
+ TimeElapsedColumn,
+ TimeRemainingColumn,
+ TransferSpeedColumn,
+)
-from pip._vendor.progress.bar import Bar, FillingCirclesBar, IncrementalBar
-from pip._vendor.progress.spinner import Spinner
-
-from pip._internal.utils.compat import WINDOWS
from pip._internal.utils.logging import get_indentation
-from pip._internal.utils.misc import format_size
-
-try:
- from pip._vendor import colorama
-# Lots of different errors can come from this, including SystemError and
-# ImportError.
-except Exception:
- colorama = None
-
-
-def _select_progress_class(preferred: Bar, fallback: Bar) -> Bar:
- encoding = getattr(preferred.file, "encoding", None)
- # If we don't know what encoding this file is in, then we'll just assume
- # that it doesn't support unicode and use the ASCII bar.
- if not encoding:
- return fallback
-
- # Collect all of the possible characters we want to use with the preferred
- # bar.
- characters = [
- getattr(preferred, "empty_fill", ""),
- getattr(preferred, "fill", ""),
- ]
- characters += list(getattr(preferred, "phases", []))
-
- # Try to decode the characters we're using for the bar using the encoding
- # of the given file, if this works then we'll assume that we can use the
- # fancier bar and if not we'll fall back to the plaintext bar.
- try:
- "".join(characters).encode(encoding)
- except UnicodeEncodeError:
- return fallback
+DownloadProgressRenderer = Callable[[Iterable[bytes]], Iterator[bytes]]
+
+
+def _rich_progress_bar(
+ iterable: Iterable[bytes],
+ *,
+ bar_type: str,
+ size: int,
+) -> Generator[bytes, None, None]:
+ assert bar_type == "on", "This should only be used in the default mode."
+
+ if not size:
+ total = float("inf")
+ columns: Tuple[ProgressColumn, ...] = (
+ TextColumn("[progress.description]{task.description}"),
+ SpinnerColumn("line", speed=1.5),
+ FileSizeColumn(),
+ TransferSpeedColumn(),
+ TimeElapsedColumn(),
+ )
else:
- return preferred
-
-
-_BaseBar: Any = _select_progress_class(IncrementalBar, Bar)
-
-
-class InterruptibleMixin:
- """
- Helper to ensure that self.finish() gets called on keyboard interrupt.
-
- This allows downloads to be interrupted without leaving temporary state
- (like hidden cursors) behind.
-
- This class is similar to the progress library's existing SigIntMixin
- helper, but as of version 1.2, that helper has the following problems:
-
- 1. It calls sys.exit().
- 2. It discards the existing SIGINT handler completely.
- 3. It leaves its own handler in place even after an uninterrupted finish,
- which will have unexpected delayed effects if the user triggers an
- unrelated keyboard interrupt some time after a progress-displaying
- download has already completed, for example.
- """
-
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- """
- Save the original SIGINT handler for later.
- """
- # https://github.com/python/mypy/issues/5887
- super().__init__(*args, **kwargs) # type: ignore
-
- self.original_handler = signal(SIGINT, self.handle_sigint)
-
- # If signal() returns None, the previous handler was not installed from
- # Python, and we cannot restore it. This probably should not happen,
- # but if it does, we must restore something sensible instead, at least.
- # The least bad option should be Python's default SIGINT handler, which
- # just raises KeyboardInterrupt.
- if self.original_handler is None:
- self.original_handler = default_int_handler
-
- def finish(self) -> None:
- """
- Restore the original SIGINT handler after finishing.
-
- This should happen regardless of whether the progress display finishes
- normally, or gets interrupted.
- """
- super().finish() # type: ignore
- signal(SIGINT, self.original_handler)
-
- def handle_sigint(self, signum, frame): # type: ignore
- """
- Call self.finish() before delegating to the original SIGINT handler.
-
- This handler should only be in place while the progress display is
- active.
- """
- self.finish()
- self.original_handler(signum, frame)
-
-
-class SilentBar(Bar):
- def update(self) -> None:
- pass
-
-
-class BlueEmojiBar(IncrementalBar):
-
- suffix = "%(percent)d%%"
- bar_prefix = " "
- bar_suffix = " "
- phases = ("\U0001F539", "\U0001F537", "\U0001F535")
-
-
-class DownloadProgressMixin:
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- # https://github.com/python/mypy/issues/5887
- super().__init__(*args, **kwargs) # type: ignore
- self.message: str = (" " * (get_indentation() + 2)) + self.message
-
- @property
- def downloaded(self) -> str:
- return format_size(self.index) # type: ignore
-
- @property
- def download_speed(self) -> str:
- # Avoid zero division errors...
- if self.avg == 0.0: # type: ignore
- return "..."
- return format_size(1 / self.avg) + "/s" # type: ignore
-
- @property
- def pretty_eta(self) -> str:
- if self.eta: # type: ignore
- return f"eta {self.eta_td}" # type: ignore
- return ""
-
- def iter(self, it): # type: ignore
- for x in it:
- yield x
- # B305 is incorrectly raised here
- # https://github.com/PyCQA/flake8-bugbear/issues/59
- self.next(len(x)) # noqa: B305
- self.finish()
-
-
-class WindowsMixin:
- def __init__(self, *args: Any, **kwargs: Any) -> None:
- # The Windows terminal does not support the hide/show cursor ANSI codes
- # even with colorama. So we'll ensure that hide_cursor is False on
- # Windows.
- # This call needs to go before the super() call, so that hide_cursor
- # is set in time. The base progress bar class writes the "hide cursor"
- # code to the terminal in its init, so if we don't set this soon
- # enough, we get a "hide" with no corresponding "show"...
- if WINDOWS and self.hide_cursor: # type: ignore
- self.hide_cursor = False
-
- # https://github.com/python/mypy/issues/5887
- super().__init__(*args, **kwargs) # type: ignore
-
- # Check if we are running on Windows and we have the colorama module,
- # if we do then wrap our file with it.
- if WINDOWS and colorama:
- self.file = colorama.AnsiToWin32(self.file) # type: ignore
- # The progress code expects to be able to call self.file.isatty()
- # but the colorama.AnsiToWin32() object doesn't have that, so we'll
- # add it.
- self.file.isatty = lambda: self.file.wrapped.isatty()
- # The progress code expects to be able to call self.file.flush()
- # but the colorama.AnsiToWin32() object doesn't have that, so we'll
- # add it.
- self.file.flush = lambda: self.file.wrapped.flush()
-
-
-class BaseDownloadProgressBar(WindowsMixin, InterruptibleMixin, DownloadProgressMixin):
-
- file = sys.stdout
- message = "%(percent)d%%"
- suffix = "%(downloaded)s %(download_speed)s %(pretty_eta)s"
-
-
-class DefaultDownloadProgressBar(BaseDownloadProgressBar, _BaseBar):
- pass
-
-
-class DownloadSilentBar(BaseDownloadProgressBar, SilentBar):
- pass
-
-
-class DownloadBar(BaseDownloadProgressBar, Bar):
- pass
-
-
-class DownloadFillingCirclesBar(BaseDownloadProgressBar, FillingCirclesBar):
- pass
-
-
-class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar, BlueEmojiBar):
- pass
-
-
-class DownloadProgressSpinner(
- WindowsMixin, InterruptibleMixin, DownloadProgressMixin, Spinner
-):
-
- file = sys.stdout
- suffix = "%(downloaded)s %(download_speed)s"
-
- def next_phase(self) -> str:
- if not hasattr(self, "_phaser"):
- self._phaser = itertools.cycle(self.phases)
- return next(self._phaser)
-
- def update(self) -> None:
- message = self.message % self
- phase = self.next_phase()
- suffix = self.suffix % self
- line = "".join(
- [
- message,
- " " if message else "",
- phase,
- " " if suffix else "",
- suffix,
- ]
+ total = size
+ columns = (
+ TextColumn("[progress.description]{task.description}"),
+ BarColumn(),
+ DownloadColumn(),
+ TransferSpeedColumn(),
+ TextColumn("eta"),
+ TimeRemainingColumn(),
)
- self.writeln(line)
-
+ progress = Progress(*columns, refresh_per_second=30)
+ task_id = progress.add_task(" " * (get_indentation() + 2), total=total)
+ with progress:
+ for chunk in iterable:
+ yield chunk
+ progress.update(task_id, advance=len(chunk))
-BAR_TYPES = {
- "off": (DownloadSilentBar, DownloadSilentBar),
- "on": (DefaultDownloadProgressBar, DownloadProgressSpinner),
- "ascii": (DownloadBar, DownloadProgressSpinner),
- "pretty": (DownloadFillingCirclesBar, DownloadProgressSpinner),
- "emoji": (DownloadBlueEmojiProgressBar, DownloadProgressSpinner),
-}
+def get_download_progress_renderer(
+ *, bar_type: str, size: Optional[int] = None
+) -> DownloadProgressRenderer:
+ """Get an object that can be used to render the download progress.
-def DownloadProgressProvider(progress_bar, max=None): # type: ignore
- if max is None or max == 0:
- return BAR_TYPES[progress_bar][1]().iter
+ Returns a callable, that takes an iterable to "wrap".
+ """
+ if bar_type == "on":
+ return functools.partial(_rich_progress_bar, bar_type=bar_type, size=size)
else:
- return BAR_TYPES[progress_bar][0](max=max).iter
+ return iter # no-op, when passed an iterator
diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py
index dbd15cbce..048b9c99e 100644
--- a/src/pip/_internal/cli/req_command.py
+++ b/src/pip/_internal/cli/req_command.py
@@ -10,7 +10,7 @@ import os
import sys
from functools import partial
from optparse import Values
-from typing import Any, List, Optional, Tuple
+from typing import TYPE_CHECKING, Any, List, Optional, Tuple
from pip._internal.cache import WheelCache
from pip._internal.cli import cmdoptions
@@ -22,6 +22,7 @@ from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.selection_prefs import SelectionPreferences
from pip._internal.models.target_python import TargetPython
from pip._internal.network.session import PipSession
+from pip._internal.operations.build.build_tracker import BuildTracker
from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req.constructors import (
install_req_from_editable,
@@ -31,10 +32,8 @@ from pip._internal.req.constructors import (
)
from pip._internal.req.req_file import parse_requirements
from pip._internal.req.req_install import InstallRequirement
-from pip._internal.req.req_tracker import RequirementTracker
from pip._internal.resolution.base import BaseResolver
from pip._internal.self_outdated_check import pip_self_version_check
-from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.temp_dir import (
TempDirectory,
TempDirectoryTypeRegistry,
@@ -42,9 +41,33 @@ from pip._internal.utils.temp_dir import (
)
from pip._internal.utils.virtualenv import running_under_virtualenv
+if TYPE_CHECKING:
+ from ssl import SSLContext
+
logger = logging.getLogger(__name__)
+def _create_truststore_ssl_context() -> Optional["SSLContext"]:
+ if sys.version_info < (3, 10):
+ raise CommandError("The truststore feature is only available for Python 3.10+")
+
+ try:
+ import ssl
+ except ImportError:
+ logger.warning("Disabling truststore since ssl support is missing")
+ return None
+
+ try:
+ import truststore
+ except ImportError:
+ raise CommandError(
+ "To use the truststore feature, 'truststore' must be installed into "
+ "pip's current environment."
+ )
+
+ return truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+
+
class SessionCommandMixin(CommandContextMixIn):
"""
@@ -84,15 +107,27 @@ class SessionCommandMixin(CommandContextMixIn):
options: Values,
retries: Optional[int] = None,
timeout: Optional[int] = None,
+ fallback_to_certifi: bool = False,
) -> PipSession:
- assert not options.cache_dir or os.path.isabs(options.cache_dir)
+ cache_dir = options.cache_dir
+ assert not cache_dir or os.path.isabs(cache_dir)
+
+ if "truststore" in options.features_enabled:
+ try:
+ ssl_context = _create_truststore_ssl_context()
+ except Exception:
+ if not fallback_to_certifi:
+ raise
+ ssl_context = None
+ else:
+ ssl_context = None
+
session = PipSession(
- cache=(
- os.path.join(options.cache_dir, "http") if options.cache_dir else None
- ),
+ cache=os.path.join(cache_dir, "http") if cache_dir else None,
retries=retries if retries is not None else options.retries,
trusted_hosts=options.trusted_hosts,
index_urls=self._get_index_urls(options),
+ ssl_context=ssl_context,
)
# Handle custom ca-bundles from the user
@@ -116,6 +151,7 @@ class SessionCommandMixin(CommandContextMixIn):
# Determine if we can prompt the user for authentication or not
session.auth.prompting = not options.no_input
+ session.auth.keyring_provider = options.keyring_provider
return session
@@ -142,7 +178,14 @@ class IndexGroupCommand(Command, SessionCommandMixin):
# Otherwise, check if we're using the latest version of pip available.
session = self._build_session(
- options, retries=0, timeout=min(5, options.timeout)
+ options,
+ retries=0,
+ timeout=min(5, options.timeout),
+ # This is set to ensure the function does not fail when truststore is
+ # specified in use-feature but cannot be loaded. This usually raises a
+ # CommandError and shows a nice user-facing error, but this function is not
+ # called in that try-except block.
+ fallback_to_certifi=True,
)
with session:
pip_self_version_check(session, options)
@@ -232,11 +275,12 @@ class RequirementCommand(IndexGroupCommand):
cls,
temp_build_dir: TempDirectory,
options: Values,
- req_tracker: RequirementTracker,
+ build_tracker: BuildTracker,
session: PipSession,
finder: PackageFinder,
use_user_site: bool,
download_dir: Optional[str] = None,
+ verbosity: int = 0,
) -> RequirementPreparer:
"""
Create a RequirementPreparer instance for the given parameters.
@@ -262,33 +306,20 @@ class RequirementCommand(IndexGroupCommand):
"fast-deps has no effect when used with the legacy resolver."
)
- in_tree_build = "out-of-tree-build" not in options.deprecated_features_enabled
- if "in-tree-build" in options.features_enabled:
- deprecated(
- reason="In-tree builds are now the default.",
- replacement="to remove the --use-feature=in-tree-build flag",
- gone_in="22.1",
- )
- if "out-of-tree-build" in options.deprecated_features_enabled:
- deprecated(
- reason="Out-of-tree builds are deprecated.",
- replacement=None,
- gone_in="22.1",
- )
-
return RequirementPreparer(
build_dir=temp_build_dir_path,
src_dir=options.src_dir,
download_dir=download_dir,
build_isolation=options.build_isolation,
- req_tracker=req_tracker,
+ check_build_deps=options.check_build_deps,
+ build_tracker=build_tracker,
session=session,
progress_bar=options.progress_bar,
finder=finder,
require_hashes=options.require_hashes,
use_user_site=use_user_site,
lazy_wheel=lazy_wheel,
- in_tree_build=in_tree_build,
+ verbosity=verbosity,
)
@classmethod
@@ -313,6 +344,7 @@ class RequirementCommand(IndexGroupCommand):
install_req_from_req_string,
isolated=options.isolated_mode,
use_pep517=use_pep517,
+ config_settings=getattr(options, "config_settings", None),
)
resolver_variant = cls.determine_resolver_variant(options)
# The long import name and duplicated invocation is needed to convince
@@ -383,6 +415,7 @@ class RequirementCommand(IndexGroupCommand):
isolated=options.isolated_mode,
use_pep517=options.use_pep517,
user_supplied=True,
+ config_settings=getattr(options, "config_settings", None),
)
requirements.append(req_to_add)
@@ -392,6 +425,7 @@ class RequirementCommand(IndexGroupCommand):
user_supplied=True,
isolated=options.isolated_mode,
use_pep517=options.use_pep517,
+ config_settings=getattr(options, "config_settings", None),
)
requirements.append(req_to_add)
diff --git a/src/pip/_internal/cli/spinners.py b/src/pip/_internal/cli/spinners.py
index 1e313e109..cf2b976f3 100644
--- a/src/pip/_internal/cli/spinners.py
+++ b/src/pip/_internal/cli/spinners.py
@@ -3,9 +3,7 @@ import itertools
import logging
import sys
import time
-from typing import IO, Iterator
-
-from pip._vendor.progress import HIDE_CURSOR, SHOW_CURSOR
+from typing import IO, Generator, Optional
from pip._internal.utils.compat import WINDOWS
from pip._internal.utils.logging import get_indentation
@@ -25,7 +23,7 @@ class InteractiveSpinner(SpinnerInterface):
def __init__(
self,
message: str,
- file: IO[str] = None,
+ file: Optional[IO[str]] = None,
spin_chars: str = "-\\|/",
# Empirically, 8 updates/second looks nice
min_update_interval_seconds: float = 0.125,
@@ -115,7 +113,7 @@ class RateLimiter:
@contextlib.contextmanager
-def open_spinner(message: str) -> Iterator[SpinnerInterface]:
+def open_spinner(message: str) -> Generator[SpinnerInterface, None, None]:
# Interactive spinner goes directly to sys.stdout rather than being routed
# through the logging system, but it acts like it has level INFO,
# i.e. it's only displayed if we're at level INFO or better.
@@ -138,8 +136,12 @@ def open_spinner(message: str) -> Iterator[SpinnerInterface]:
spinner.finish("done")
+HIDE_CURSOR = "\x1b[?25l"
+SHOW_CURSOR = "\x1b[?25h"
+
+
@contextlib.contextmanager
-def hidden_cursor(file: IO[str]) -> Iterator[None]:
+def hidden_cursor(file: IO[str]) -> Generator[None, None, None]:
# The Windows terminal does not support the hide/show cursor ANSI codes,
# even via colorama. So don't even try.
if WINDOWS:
diff --git a/src/pip/_internal/commands/__init__.py b/src/pip/_internal/commands/__init__.py
index c72f24f30..858a41014 100644
--- a/src/pip/_internal/commands/__init__.py
+++ b/src/pip/_internal/commands/__init__.py
@@ -38,6 +38,11 @@ commands_dict: Dict[str, CommandInfo] = {
"FreezeCommand",
"Output installed packages in requirements format.",
),
+ "inspect": CommandInfo(
+ "pip._internal.commands.inspect",
+ "InspectCommand",
+ "Inspect the python environment.",
+ ),
"list": CommandInfo(
"pip._internal.commands.list",
"ListCommand",
diff --git a/src/pip/_internal/commands/cache.py b/src/pip/_internal/commands/cache.py
index f1a489d32..c5f03302d 100644
--- a/src/pip/_internal/commands/cache.py
+++ b/src/pip/_internal/commands/cache.py
@@ -105,9 +105,9 @@ class CacheCommand(Command):
Package index page cache location: {http_cache_location}
Package index page cache size: {http_cache_size}
Number of HTTP files: {num_http_files}
- Wheels location: {wheels_cache_location}
- Wheels size: {wheels_cache_size}
- Number of wheels: {package_count}
+ Locally built wheels location: {wheels_cache_location}
+ Locally built wheels size: {wheels_cache_size}
+ Number of locally built wheels: {package_count}
"""
)
.format(
@@ -140,7 +140,7 @@ class CacheCommand(Command):
def format_for_human(self, files: List[str]) -> None:
if not files:
- logger.info("Nothing cached.")
+ logger.info("No locally built wheels cached.")
return
results = []
diff --git a/src/pip/_internal/commands/completion.py b/src/pip/_internal/commands/completion.py
index c0fb4caf8..deaa30899 100644
--- a/src/pip/_internal/commands/completion.py
+++ b/src/pip/_internal/commands/completion.py
@@ -43,6 +43,28 @@ COMPLETION_SCRIPTS = {
end
complete -fa "(__fish_complete_pip)" -c {prog}
""",
+ "powershell": """
+ if ((Test-Path Function:\\TabExpansion) -and -not `
+ (Test-Path Function:\\_pip_completeBackup)) {{
+ Rename-Item Function:\\TabExpansion _pip_completeBackup
+ }}
+ function TabExpansion($line, $lastWord) {{
+ $lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart()
+ if ($lastBlock.StartsWith("{prog} ")) {{
+ $Env:COMP_WORDS=$lastBlock
+ $Env:COMP_CWORD=$lastBlock.Split().Length - 1
+ $Env:PIP_AUTO_COMPLETE=1
+ (& {prog}).Split()
+ Remove-Item Env:COMP_WORDS
+ Remove-Item Env:COMP_CWORD
+ Remove-Item Env:PIP_AUTO_COMPLETE
+ }}
+ elseif (Test-Path Function:\\_pip_completeBackup) {{
+ # Fall back on existing tab expansion
+ _pip_completeBackup $line $lastWord
+ }}
+ }}
+ """,
}
@@ -76,6 +98,14 @@ class CompletionCommand(Command):
dest="shell",
help="Emit completion code for fish",
)
+ self.cmd_opts.add_option(
+ "--powershell",
+ "-p",
+ action="store_const",
+ const="powershell",
+ dest="shell",
+ help="Emit completion code for powershell",
+ )
self.parser.insert_option_group(0, self.cmd_opts)
diff --git a/src/pip/_internal/commands/configuration.py b/src/pip/_internal/commands/configuration.py
index c6c74ed50..84b134e49 100644
--- a/src/pip/_internal/commands/configuration.py
+++ b/src/pip/_internal/commands/configuration.py
@@ -27,11 +27,17 @@ class ConfigurationCommand(Command):
- list: List the active configuration (or from the file specified)
- edit: Edit the configuration file in an editor
- - get: Get the value associated with name
- - set: Set the name=value
- - unset: Unset the value associated with name
+ - get: Get the value associated with command.option
+ - set: Set the command.option=value
+ - unset: Unset the value associated with command.option
- debug: List the configuration files and values defined under them
+ Configuration keys should be dot separated command and option name,
+ with the special prefix "global" affecting any command. For example,
+ "pip config set global.index-url https://example.org/" would configure
+ the index url for all commands, but "pip config set download.timeout 10"
+ would configure a 10 second timeout only for "pip download" commands.
+
If none of --user, --global and --site are passed, a virtual
environment configuration file is used if one is active and the file
exists. Otherwise, all modifications happen to the user file by
@@ -43,9 +49,9 @@ class ConfigurationCommand(Command):
%prog [<file-option>] list
%prog [<file-option>] [--editor <editor-path>] edit
- %prog [<file-option>] get name
- %prog [<file-option>] set name value
- %prog [<file-option>] unset name
+ %prog [<file-option>] get command.option
+ %prog [<file-option>] set command.option value
+ %prog [<file-option>] unset command.option
%prog [<file-option>] debug
"""
@@ -222,9 +228,19 @@ class ConfigurationCommand(Command):
fname = self.configuration.get_file_to_edit()
if fname is None:
raise PipError("Could not determine appropriate file.")
+ elif '"' in fname:
+ # This shouldn't happen, unless we see a username like that.
+ # If that happens, we'd appreciate a pull request fixing this.
+ raise PipError(
+ f'Can not open an editor for a file name containing "\n{fname}'
+ )
try:
- subprocess.check_call([editor, fname])
+ subprocess.check_call(f'{editor} "{fname}"', shell=True)
+ except FileNotFoundError as e:
+ if not e.filename:
+ e.filename = editor
+ raise
except subprocess.CalledProcessError as e:
raise PipError(
"Editor Subprocess exited with exit code {}".format(e.returncode)
diff --git a/src/pip/_internal/commands/debug.py b/src/pip/_internal/commands/debug.py
index d3f1f28de..2a3e7d298 100644
--- a/src/pip/_internal/commands/debug.py
+++ b/src/pip/_internal/commands/debug.py
@@ -1,3 +1,4 @@
+import importlib.resources
import locale
import logging
import os
@@ -10,7 +11,6 @@ import pip._vendor
from pip._vendor.certifi import where
from pip._vendor.packaging.version import parse as parse_version
-from pip import __file__ as pip_location
from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import Command
from pip._internal.cli.cmdoptions import make_target_python
@@ -35,11 +35,7 @@ def show_sys_implementation() -> None:
def create_vendor_txt_map() -> Dict[str, str]:
- vendor_txt_path = os.path.join(
- os.path.dirname(pip_location), "_vendor", "vendor.txt"
- )
-
- with open(vendor_txt_path) as f:
+ with importlib.resources.open_text("pip._vendor", "vendor.txt") as f:
# Purge non version specifying lines.
# Also, remove any space prefix or suffixes (including comments).
lines = [
@@ -47,12 +43,12 @@ def create_vendor_txt_map() -> Dict[str, str]:
]
# Transform into "module" -> version dict.
- return dict(line.split("==", 1) for line in lines) # type: ignore
+ return dict(line.split("==", 1) for line in lines)
def get_module_from_module_name(module_name: str) -> ModuleType:
# Module name can be uppercase in vendor.txt for some reason...
- module_name = module_name.lower()
+ module_name = module_name.lower().replace("-", "_")
# PATCH: setuptools is actually only pkg_resources.
if module_name == "setuptools":
module_name = "pkg_resources"
@@ -67,6 +63,7 @@ def get_vendor_version_from_module(module_name: str) -> Optional[str]:
if not version:
# Try to find version in debundled module info.
+ assert module.__file__ is not None
env = get_environment([os.path.dirname(module.__file__)])
dist = env.get_distribution(module_name)
if dist:
diff --git a/src/pip/_internal/commands/download.py b/src/pip/_internal/commands/download.py
index 7de207f13..90388d118 100644
--- a/src/pip/_internal/commands/download.py
+++ b/src/pip/_internal/commands/download.py
@@ -7,7 +7,8 @@ from pip._internal.cli import cmdoptions
from pip._internal.cli.cmdoptions import make_target_python
from pip._internal.cli.req_command import RequirementCommand, with_cleanup
from pip._internal.cli.status_codes import SUCCESS
-from pip._internal.req.req_tracker import get_requirement_tracker
+from pip._internal.operations.build.build_tracker import get_build_tracker
+from pip._internal.req.req_install import check_legacy_setup_py_options
from pip._internal.utils.misc import ensure_dir, normalize_path, write_output
from pip._internal.utils.temp_dir import TempDirectory
@@ -49,6 +50,7 @@ class DownloadCommand(RequirementCommand):
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
self.cmd_opts.add_option(cmdoptions.use_pep517())
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
+ self.cmd_opts.add_option(cmdoptions.check_build_deps())
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
self.cmd_opts.add_option(
@@ -95,7 +97,7 @@ class DownloadCommand(RequirementCommand):
ignore_requires_python=options.ignore_requires_python,
)
- req_tracker = self.enter_context(get_requirement_tracker())
+ build_tracker = self.enter_context(get_build_tracker())
directory = TempDirectory(
delete=not options.no_clean,
@@ -104,15 +106,17 @@ class DownloadCommand(RequirementCommand):
)
reqs = self.get_requirements(args, options, finder, session)
+ check_legacy_setup_py_options(options, reqs)
preparer = self.make_requirement_preparer(
temp_build_dir=directory,
options=options,
- req_tracker=req_tracker,
+ build_tracker=build_tracker,
session=session,
finder=finder,
download_dir=options.download_dir,
use_user_site=False,
+ verbosity=self.verbosity,
)
resolver = self.make_resolver(
@@ -120,6 +124,7 @@ class DownloadCommand(RequirementCommand):
finder=finder,
options=options,
ignore_requires_python=options.ignore_requires_python,
+ use_pep517=options.use_pep517,
py_version_info=options.python_version,
)
diff --git a/src/pip/_internal/commands/index.py b/src/pip/_internal/commands/index.py
index b4bf0ac06..7267effed 100644
--- a/src/pip/_internal/commands/index.py
+++ b/src/pip/_internal/commands/index.py
@@ -24,6 +24,7 @@ class IndexCommand(IndexGroupCommand):
Inspect information available from package indexes.
"""
+ ignore_require_venv = True
usage = """
%prog versions <package>
"""
diff --git a/src/pip/_internal/commands/inspect.py b/src/pip/_internal/commands/inspect.py
new file mode 100644
index 000000000..27c8fa3d5
--- /dev/null
+++ b/src/pip/_internal/commands/inspect.py
@@ -0,0 +1,92 @@
+import logging
+from optparse import Values
+from typing import Any, Dict, List
+
+from pip._vendor.packaging.markers import default_environment
+from pip._vendor.rich import print_json
+
+from pip import __version__
+from pip._internal.cli import cmdoptions
+from pip._internal.cli.req_command import Command
+from pip._internal.cli.status_codes import SUCCESS
+from pip._internal.metadata import BaseDistribution, get_environment
+from pip._internal.utils.compat import stdlib_pkgs
+from pip._internal.utils.urls import path_to_url
+
+logger = logging.getLogger(__name__)
+
+
+class InspectCommand(Command):
+ """
+ Inspect the content of a Python environment and produce a report in JSON format.
+ """
+
+ ignore_require_venv = True
+ usage = """
+ %prog [options]"""
+
+ def add_options(self) -> None:
+ self.cmd_opts.add_option(
+ "--local",
+ action="store_true",
+ default=False,
+ help=(
+ "If in a virtualenv that has global access, do not list "
+ "globally-installed packages."
+ ),
+ )
+ self.cmd_opts.add_option(
+ "--user",
+ dest="user",
+ action="store_true",
+ default=False,
+ help="Only output packages installed in user-site.",
+ )
+ self.cmd_opts.add_option(cmdoptions.list_path())
+ self.parser.insert_option_group(0, self.cmd_opts)
+
+ def run(self, options: Values, args: List[str]) -> int:
+ cmdoptions.check_list_path_option(options)
+ dists = get_environment(options.path).iter_installed_distributions(
+ local_only=options.local,
+ user_only=options.user,
+ skip=set(stdlib_pkgs),
+ )
+ output = {
+ "version": "1",
+ "pip_version": __version__,
+ "installed": [self._dist_to_dict(dist) for dist in dists],
+ "environment": default_environment(),
+ # TODO tags? scheme?
+ }
+ print_json(data=output)
+ return SUCCESS
+
+ def _dist_to_dict(self, dist: BaseDistribution) -> Dict[str, Any]:
+ res: Dict[str, Any] = {
+ "metadata": dist.metadata_dict,
+ "metadata_location": dist.info_location,
+ }
+ # direct_url. Note that we don't have download_info (as in the installation
+ # report) since it is not recorded in installed metadata.
+ direct_url = dist.direct_url
+ if direct_url is not None:
+ res["direct_url"] = direct_url.to_dict()
+ else:
+ # Emulate direct_url for legacy editable installs.
+ editable_project_location = dist.editable_project_location
+ if editable_project_location is not None:
+ res["direct_url"] = {
+ "url": path_to_url(editable_project_location),
+ "dir_info": {
+ "editable": True,
+ },
+ }
+ # installer
+ installer = dist.installer
+ if dist.installer:
+ res["installer"] = installer
+ # requested
+ if dist.installed_with_dist_info:
+ res["requested"] = dist.requested
+ return res
diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py
index eedb1ff5d..4783f807f 100644
--- a/src/pip/_internal/commands/install.py
+++ b/src/pip/_internal/commands/install.py
@@ -1,12 +1,13 @@
import errno
+import json
import operator
import os
import shutil
import site
from optparse import SUPPRESS_HELP, Values
-from typing import Iterable, List, Optional
+from typing import List, Optional
-from pip._vendor.packaging.utils import canonicalize_name
+from pip._vendor.rich import print_json
from pip._internal.cache import WheelCache
from pip._internal.cli import cmdoptions
@@ -20,16 +21,23 @@ from pip._internal.cli.status_codes import ERROR, SUCCESS
from pip._internal.exceptions import CommandError, InstallationError
from pip._internal.locations import get_scheme
from pip._internal.metadata import get_environment
-from pip._internal.models.format_control import FormatControl
+from pip._internal.models.installation_report import InstallationReport
+from pip._internal.operations.build.build_tracker import get_build_tracker
from pip._internal.operations.check import ConflictDetails, check_install_conflicts
from pip._internal.req import install_given_reqs
-from pip._internal.req.req_install import InstallRequirement
-from pip._internal.req.req_tracker import get_requirement_tracker
+from pip._internal.req.req_install import (
+ InstallRequirement,
+ check_legacy_setup_py_options,
+)
from pip._internal.utils.compat import WINDOWS
-from pip._internal.utils.distutils_args import parse_distutils_args
+from pip._internal.utils.deprecation import (
+ LegacyInstallReasonFailedBdistWheel,
+ deprecated,
+)
from pip._internal.utils.filesystem import test_writable_dir
from pip._internal.utils.logging import getLogger
from pip._internal.utils.misc import (
+ check_externally_managed,
ensure_dir,
get_pip_version,
protect_pip_from_modification_on_windows,
@@ -40,24 +48,11 @@ from pip._internal.utils.virtualenv import (
running_under_virtualenv,
virtualenv_no_global,
)
-from pip._internal.wheel_builder import (
- BinaryAllowedPredicate,
- build,
- should_build_for_install_command,
-)
+from pip._internal.wheel_builder import build, should_build_for_install_command
logger = getLogger(__name__)
-def get_check_binary_allowed(format_control: FormatControl) -> BinaryAllowedPredicate:
- def check_binary_allowed(req: InstallRequirement) -> bool:
- canonical_name = canonicalize_name(req.name or "")
- allowed_formats = format_control.get_allowed_formats(canonical_name)
- return "binary" in allowed_formats
-
- return check_binary_allowed
-
-
class InstallCommand(RequirementCommand):
"""
Install packages from:
@@ -86,6 +81,17 @@ class InstallCommand(RequirementCommand):
self.cmd_opts.add_option(cmdoptions.editable())
self.cmd_opts.add_option(
+ "--dry-run",
+ action="store_true",
+ dest="dry_run",
+ default=False,
+ help=(
+ "Don't actually install anything, just print what would be. "
+ "Can be used in combination with --ignore-installed "
+ "to 'resolve' the requirements."
+ ),
+ )
+ self.cmd_opts.add_option(
"-t",
"--target",
dest="target_dir",
@@ -131,7 +137,12 @@ class InstallCommand(RequirementCommand):
default=None,
help=(
"Installation prefix where lib, bin and other top-level "
- "folders are placed"
+ "folders are placed. Note that the resulting installation may "
+ "contain scripts and other resources which reference the "
+ "Python interpreter of pip, and not that of ``--prefix``. "
+ "See also the ``--python`` option if the intention is to "
+ "install packages into another (possibly pip-free) "
+ "environment."
),
)
@@ -189,8 +200,10 @@ class InstallCommand(RequirementCommand):
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
self.cmd_opts.add_option(cmdoptions.use_pep517())
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
+ self.cmd_opts.add_option(cmdoptions.check_build_deps())
+ self.cmd_opts.add_option(cmdoptions.override_externally_managed())
- self.cmd_opts.add_option(cmdoptions.install_options())
+ self.cmd_opts.add_option(cmdoptions.config_settings())
self.cmd_opts.add_option(cmdoptions.global_options())
self.cmd_opts.add_option(
@@ -222,12 +235,12 @@ class InstallCommand(RequirementCommand):
default=True,
help="Do not warn about broken dependencies",
)
-
self.cmd_opts.add_option(cmdoptions.no_binary())
self.cmd_opts.add_option(cmdoptions.only_binary())
self.cmd_opts.add_option(cmdoptions.prefer_binary())
self.cmd_opts.add_option(cmdoptions.require_hashes())
self.cmd_opts.add_option(cmdoptions.progress_bar())
+ self.cmd_opts.add_option(cmdoptions.root_user_action())
index_opts = cmdoptions.make_option_group(
cmdoptions.index_group,
@@ -237,20 +250,50 @@ class InstallCommand(RequirementCommand):
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, self.cmd_opts)
+ self.cmd_opts.add_option(
+ "--report",
+ dest="json_report_file",
+ metavar="file",
+ default=None,
+ help=(
+ "Generate a JSON file describing what pip did to install "
+ "the provided requirements. "
+ "Can be used in combination with --dry-run and --ignore-installed "
+ "to 'resolve' the requirements. "
+ "When - is used as file name it writes to stdout. "
+ "When writing to stdout, please combine with the --quiet option "
+ "to avoid mixing pip logging output with JSON output."
+ ),
+ )
+
@with_cleanup
def run(self, options: Values, args: List[str]) -> int:
if options.use_user_site and options.target_dir is not None:
raise CommandError("Can not combine '--user' and '--target'")
- cmdoptions.check_install_build_global(options)
+ # Check whether the environment we're installing into is externally
+ # managed, as specified in PEP 668. Specifying --root, --target, or
+ # --prefix disables the check, since there's no reliable way to locate
+ # the EXTERNALLY-MANAGED file for those cases. An exception is also
+ # made specifically for "--dry-run --report" for convenience.
+ installing_into_current_environment = (
+ not (options.dry_run and options.json_report_file)
+ and options.root_path is None
+ and options.target_dir is None
+ and options.prefix_path is None
+ )
+ if (
+ installing_into_current_environment
+ and not options.override_externally_managed
+ ):
+ check_externally_managed()
+
upgrade_strategy = "to-satisfy-only"
if options.upgrade:
upgrade_strategy = options.upgrade_strategy
cmdoptions.check_dist_restriction(options, check_target=True)
- install_options = options.install_options or []
-
logger.verbose("Using %s", get_pip_version())
options.use_user_site = decide_user_install(
options.use_user_site,
@@ -291,9 +334,7 @@ class InstallCommand(RequirementCommand):
target_python=target_python,
ignore_requires_python=options.ignore_requires_python,
)
- wheel_cache = WheelCache(options.cache_dir, options.format_control)
-
- req_tracker = self.enter_context(get_requirement_tracker())
+ build_tracker = self.enter_context(get_build_tracker())
directory = TempDirectory(
delete=not options.no_clean,
@@ -303,6 +344,26 @@ class InstallCommand(RequirementCommand):
try:
reqs = self.get_requirements(args, options, finder, session)
+ check_legacy_setup_py_options(options, reqs)
+
+ if "no-binary-enable-wheel-cache" in options.features_enabled:
+ # TODO: remove format_control from WheelCache when the deprecation cycle
+ # is over
+ wheel_cache = WheelCache(options.cache_dir)
+ else:
+ if options.format_control.no_binary:
+ deprecated(
+ reason=(
+ "--no-binary currently disables reading from "
+ "the cache of locally built wheels. In the future "
+ "--no-binary will not influence the wheel cache."
+ ),
+ replacement="to use the --no-cache-dir option",
+ feature_flag="no-binary-enable-wheel-cache",
+ issue=11453,
+ gone_in="23.1",
+ )
+ wheel_cache = WheelCache(options.cache_dir, options.format_control)
# Only when installing is it permitted to use PEP 660.
# In other circumstances (pip wheel, pip download) we generate
@@ -310,15 +371,14 @@ class InstallCommand(RequirementCommand):
for req in reqs:
req.permit_editable_wheels = True
- reject_location_related_install_options(reqs, options.install_options)
-
preparer = self.make_requirement_preparer(
temp_build_dir=directory,
options=options,
- req_tracker=req_tracker,
+ build_tracker=build_tracker,
session=session,
finder=finder,
use_user_site=options.use_user_site,
+ verbosity=self.verbosity,
)
resolver = self.make_resolver(
preparer=preparer,
@@ -339,6 +399,26 @@ class InstallCommand(RequirementCommand):
reqs, check_supported_wheels=not options.target_dir
)
+ if options.json_report_file:
+ report = InstallationReport(requirement_set.requirements_to_install)
+ if options.json_report_file == "-":
+ print_json(data=report.to_dict())
+ else:
+ with open(options.json_report_file, "w", encoding="utf-8") as f:
+ json.dump(report.to_dict(), f, indent=2, ensure_ascii=False)
+
+ if options.dry_run:
+ would_install_items = sorted(
+ (r.metadata["name"], r.metadata["version"])
+ for r in requirement_set.requirements_to_install
+ )
+ if would_install_items:
+ write_output(
+ "Would install %s",
+ " ".join("-".join(item) for item in would_install_items),
+ )
+ return SUCCESS
+
try:
pip_req = requirement_set.get_requirement("pip")
except KeyError:
@@ -349,12 +429,10 @@ class InstallCommand(RequirementCommand):
modifying_pip = pip_req.satisfied_by is None
protect_pip_from_modification_on_windows(modifying_pip=modifying_pip)
- check_binary_allowed = get_check_binary_allowed(finder.format_control)
-
reqs_to_build = [
r
for r in requirement_set.requirements.values()
- if should_build_for_install_command(r, check_binary_allowed)
+ if should_build_for_install_command(r)
]
_, build_failures = build(
@@ -362,7 +440,7 @@ class InstallCommand(RequirementCommand):
wheel_cache=wheel_cache,
verify=True,
build_options=[],
- global_options=[],
+ global_options=global_options,
)
# If we're using PEP 517, we cannot do a legacy setup.py install
@@ -383,7 +461,7 @@ class InstallCommand(RequirementCommand):
# those.
for r in build_failures:
if not r.use_pep517:
- r.legacy_install_reason = 8368
+ r.legacy_install_reason = LegacyInstallReasonFailedBdistWheel
to_install = resolver.get_installation_order(requirement_set)
@@ -403,7 +481,6 @@ class InstallCommand(RequirementCommand):
installed = install_given_reqs(
to_install,
- install_options,
global_options,
root=options.root_path,
home=target_temp_dir_path,
@@ -463,8 +540,8 @@ class InstallCommand(RequirementCommand):
self._handle_target_dir(
options.target_dir, target_temp_dir, options.upgrade
)
-
- warn_if_run_as_root()
+ if options.root_user_action == "warn":
+ warn_if_run_as_root()
return SUCCESS
def _handle_target_dir(
@@ -674,45 +751,6 @@ def decide_user_install(
return True
-def reject_location_related_install_options(
- requirements: List[InstallRequirement], options: Optional[List[str]]
-) -> None:
- """If any location-changing --install-option arguments were passed for
- requirements or on the command-line, then show a deprecation warning.
- """
-
- def format_options(option_names: Iterable[str]) -> List[str]:
- return ["--{}".format(name.replace("_", "-")) for name in option_names]
-
- offenders = []
-
- for requirement in requirements:
- install_options = requirement.install_options
- location_options = parse_distutils_args(install_options)
- if location_options:
- offenders.append(
- "{!r} from {}".format(
- format_options(location_options.keys()), requirement
- )
- )
-
- if options:
- location_options = parse_distutils_args(options)
- if location_options:
- offenders.append(
- "{!r} from command line".format(format_options(location_options.keys()))
- )
-
- if not offenders:
- return
-
- raise CommandError(
- "Location-changing options found in --install-option: {}."
- " This is unsupported, use pip-level options like --user,"
- " --prefix, --root, and --target instead.".format("; ".join(offenders))
- )
-
-
def create_os_error_message(
error: OSError, show_traceback: bool, using_user_site: bool
) -> str:
diff --git a/src/pip/_internal/commands/list.py b/src/pip/_internal/commands/list.py
index 75d8dd465..8e1426dbb 100644
--- a/src/pip/_internal/commands/list.py
+++ b/src/pip/_internal/commands/list.py
@@ -1,7 +1,7 @@
import json
import logging
from optparse import Values
-from typing import TYPE_CHECKING, Iterator, List, Optional, Sequence, Tuple, cast
+from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast
from pip._vendor.packaging.utils import canonicalize_name
@@ -16,7 +16,6 @@ from pip._internal.models.selection_prefs import SelectionPreferences
from pip._internal.network.session import PipSession
from pip._internal.utils.compat import stdlib_pkgs
from pip._internal.utils.misc import tabulate, write_output
-from pip._internal.utils.parallel import map_multithread
if TYPE_CHECKING:
from pip._internal.metadata.base import DistributionVersion
@@ -156,6 +155,11 @@ class ListCommand(IndexGroupCommand):
if options.outdated and options.uptodate:
raise CommandError("Options --outdated and --uptodate cannot be combined.")
+ if options.outdated and options.list_format == "freeze":
+ raise CommandError(
+ "List format 'freeze' can not be used with the --outdated option."
+ )
+
cmdoptions.check_list_path_option(options)
skip = set(stdlib_pkgs)
@@ -222,7 +226,7 @@ class ListCommand(IndexGroupCommand):
def iter_packages_latest_infos(
self, packages: "_ProcessedDists", options: Values
- ) -> Iterator["_DistWithLatestInfo"]:
+ ) -> Generator["_DistWithLatestInfo", None, None]:
with self._build_session(options) as session:
finder = self._build_package_finder(options, session)
@@ -254,7 +258,7 @@ class ListCommand(IndexGroupCommand):
dist.latest_filetype = typ
return dist
- for dist in map_multithread(latest_info, packages):
+ for dist in map(latest_info, packages):
if dist is not None:
yield dist
diff --git a/src/pip/_internal/commands/show.py b/src/pip/_internal/commands/show.py
index 872292a2b..3f10701f6 100644
--- a/src/pip/_internal/commands/show.py
+++ b/src/pip/_internal/commands/show.py
@@ -1,8 +1,6 @@
-import csv
import logging
-import pathlib
from optparse import Values
-from typing import Iterator, List, NamedTuple, Optional, Tuple
+from typing import Generator, Iterable, Iterator, List, NamedTuple, Optional
from pip._vendor.packaging.utils import canonicalize_name
@@ -55,6 +53,7 @@ class _PackageInfo(NamedTuple):
name: str
version: str
location: str
+ editable_project_location: Optional[str]
requires: List[str]
required_by: List[str]
installer: str
@@ -62,6 +61,7 @@ class _PackageInfo(NamedTuple):
classifiers: List[str]
summary: str
homepage: str
+ project_urls: List[str]
author: str
author_email: str
license: str
@@ -69,34 +69,7 @@ class _PackageInfo(NamedTuple):
files: Optional[List[str]]
-def _convert_legacy_entry(entry: Tuple[str, ...], info: Tuple[str, ...]) -> str:
- """Convert a legacy installed-files.txt path into modern RECORD path.
-
- The legacy format stores paths relative to the info directory, while the
- modern format stores paths relative to the package root, e.g. the
- site-packages directory.
-
- :param entry: Path parts of the installed-files.txt entry.
- :param info: Path parts of the egg-info directory relative to package root.
- :returns: The converted entry.
-
- For best compatibility with symlinks, this does not use ``abspath()`` or
- ``Path.resolve()``, but tries to work with path parts:
-
- 1. While ``entry`` starts with ``..``, remove the equal amounts of parts
- from ``info``; if ``info`` is empty, start appending ``..`` instead.
- 2. Join the two directly.
- """
- while entry and entry[0] == "..":
- if not info or info[-1] == "..":
- info += ("..",)
- else:
- info = info[:-1]
- entry = entry[1:]
- return str(pathlib.Path(*info, *entry))
-
-
-def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
+def search_packages_info(query: List[str]) -> Generator[_PackageInfo, None, None]:
"""
Gather details from installed distributions. Print distribution name,
version, location, and installed files. Installed files requires a
@@ -105,7 +78,7 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
"""
env = get_default_environment()
- installed = {dist.canonical_name: dist for dist in env.iter_distributions()}
+ installed = {dist.canonical_name: dist for dist in env.iter_all_distributions()}
query_names = [canonicalize_name(name) for name in query]
missing = sorted(
[name for name, pkg in zip(query, query_names) if pkg not in installed]
@@ -121,34 +94,6 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
in {canonicalize_name(d.name) for d in dist.iter_dependencies()}
)
- def _files_from_record(dist: BaseDistribution) -> Optional[Iterator[str]]:
- try:
- text = dist.read_text("RECORD")
- except FileNotFoundError:
- return None
- # This extra Path-str cast normalizes entries.
- return (str(pathlib.Path(row[0])) for row in csv.reader(text.splitlines()))
-
- def _files_from_legacy(dist: BaseDistribution) -> Optional[Iterator[str]]:
- try:
- text = dist.read_text("installed-files.txt")
- except FileNotFoundError:
- return None
- paths = (p for p in text.splitlines(keepends=False) if p)
- root = dist.location
- info = dist.info_directory
- if root is None or info is None:
- return paths
- try:
- info_rel = pathlib.Path(info).relative_to(root)
- except ValueError: # info is not relative to root.
- return paths
- if not info_rel.parts: # info *is* root.
- return paths
- return (
- _convert_legacy_entry(pathlib.Path(p).parts, info_rel.parts) for p in paths
- )
-
for query_name in query_names:
try:
dist = installed[query_name]
@@ -164,7 +109,7 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
except FileNotFoundError:
entry_points = []
- files_iter = _files_from_record(dist) or _files_from_legacy(dist)
+ files_iter = dist.iter_declared_entries()
if files_iter is None:
files: Optional[List[str]] = None
else:
@@ -176,6 +121,7 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
name=dist.raw_name,
version=str(dist.version),
location=dist.location or "",
+ editable_project_location=dist.editable_project_location,
requires=requires,
required_by=required_by,
installer=dist.installer,
@@ -183,6 +129,7 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
classifiers=metadata.get_all("Classifier", []),
summary=metadata.get("Summary", ""),
homepage=metadata.get("Home-page", ""),
+ project_urls=metadata.get_all("Project-URL", []),
author=metadata.get("Author", ""),
author_email=metadata.get("Author-email", ""),
license=metadata.get("License", ""),
@@ -192,7 +139,7 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
def print_results(
- distributions: Iterator[_PackageInfo],
+ distributions: Iterable[_PackageInfo],
list_files: bool,
verbose: bool,
) -> bool:
@@ -213,6 +160,10 @@ def print_results(
write_output("Author-email: %s", dist.author_email)
write_output("License: %s", dist.license)
write_output("Location: %s", dist.location)
+ if dist.editable_project_location is not None:
+ write_output(
+ "Editable project location: %s", dist.editable_project_location
+ )
write_output("Requires: %s", ", ".join(dist.requires))
write_output("Required-by: %s", ", ".join(dist.required_by))
@@ -225,6 +176,9 @@ def print_results(
write_output("Entry-points:")
for entry in dist.entry_points:
write_output(" %s", entry.strip())
+ write_output("Project-URLs:")
+ for project_url in dist.project_urls:
+ write_output(" %s", project_url)
if list_files:
write_output("Files:")
if dist.files is None:
diff --git a/src/pip/_internal/commands/uninstall.py b/src/pip/_internal/commands/uninstall.py
index bb9e8e6a3..f198fc313 100644
--- a/src/pip/_internal/commands/uninstall.py
+++ b/src/pip/_internal/commands/uninstall.py
@@ -4,6 +4,7 @@ from typing import List
from pip._vendor.packaging.utils import canonicalize_name
+from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import Command
from pip._internal.cli.req_command import SessionCommandMixin, warn_if_run_as_root
from pip._internal.cli.status_codes import SUCCESS
@@ -13,7 +14,10 @@ from pip._internal.req.constructors import (
install_req_from_line,
install_req_from_parsed_requirement,
)
-from pip._internal.utils.misc import protect_pip_from_modification_on_windows
+from pip._internal.utils.misc import (
+ check_externally_managed,
+ protect_pip_from_modification_on_windows,
+)
logger = logging.getLogger(__name__)
@@ -53,7 +57,8 @@ class UninstallCommand(Command, SessionCommandMixin):
action="store_true",
help="Don't ask for confirmation of uninstall deletions.",
)
-
+ self.cmd_opts.add_option(cmdoptions.root_user_action())
+ self.cmd_opts.add_option(cmdoptions.override_externally_managed())
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options: Values, args: List[str]) -> int:
@@ -89,6 +94,9 @@ class UninstallCommand(Command, SessionCommandMixin):
f'"pip help {self.name}")'
)
+ if not options.override_externally_managed:
+ check_externally_managed()
+
protect_pip_from_modification_on_windows(
modifying_pip="pip" in reqs_to_uninstall
)
@@ -100,6 +108,6 @@ class UninstallCommand(Command, SessionCommandMixin):
)
if uninstall_pathset:
uninstall_pathset.commit()
-
- warn_if_run_as_root()
+ if options.root_user_action == "warn":
+ warn_if_run_as_root()
return SUCCESS
diff --git a/src/pip/_internal/commands/wheel.py b/src/pip/_internal/commands/wheel.py
index cea81ee51..a8483559c 100644
--- a/src/pip/_internal/commands/wheel.py
+++ b/src/pip/_internal/commands/wheel.py
@@ -9,8 +9,12 @@ from pip._internal.cli import cmdoptions
from pip._internal.cli.req_command import RequirementCommand, with_cleanup
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.exceptions import CommandError
-from pip._internal.req.req_install import InstallRequirement
-from pip._internal.req.req_tracker import get_requirement_tracker
+from pip._internal.operations.build.build_tracker import get_build_tracker
+from pip._internal.req.req_install import (
+ InstallRequirement,
+ check_legacy_setup_py_options,
+)
+from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.misc import ensure_dir, normalize_path
from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.wheel_builder import build, should_build_for_wheel_command
@@ -26,10 +30,8 @@ class WheelCommand(RequirementCommand):
recompiling your software during every install. For more details, see the
wheel docs: https://wheel.readthedocs.io/en/latest/
- Requirements: setuptools>=0.8, and wheel.
-
- 'pip wheel' uses the bdist_wheel setuptools extension from the wheel
- package to build individual wheels.
+ 'pip wheel' uses the build system interface as described here:
+ https://pip.pypa.io/en/stable/reference/build-system/
"""
@@ -59,6 +61,7 @@ class WheelCommand(RequirementCommand):
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
self.cmd_opts.add_option(cmdoptions.use_pep517())
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
+ self.cmd_opts.add_option(cmdoptions.check_build_deps())
self.cmd_opts.add_option(cmdoptions.constraints())
self.cmd_opts.add_option(cmdoptions.editable())
self.cmd_opts.add_option(cmdoptions.requirements())
@@ -75,6 +78,7 @@ class WheelCommand(RequirementCommand):
help="Don't verify if built wheel is valid.",
)
+ self.cmd_opts.add_option(cmdoptions.config_settings())
self.cmd_opts.add_option(cmdoptions.build_options())
self.cmd_opts.add_option(cmdoptions.global_options())
@@ -100,8 +104,6 @@ class WheelCommand(RequirementCommand):
@with_cleanup
def run(self, options: Values, args: List[str]) -> int:
- cmdoptions.check_install_build_global(options)
-
session = self.get_default_session(options)
finder = self._build_package_finder(options, session)
@@ -110,7 +112,7 @@ class WheelCommand(RequirementCommand):
options.wheel_dir = normalize_path(options.wheel_dir)
ensure_dir(options.wheel_dir)
- req_tracker = self.enter_context(get_requirement_tracker())
+ build_tracker = self.enter_context(get_build_tracker())
directory = TempDirectory(
delete=not options.no_clean,
@@ -119,15 +121,36 @@ class WheelCommand(RequirementCommand):
)
reqs = self.get_requirements(args, options, finder, session)
+ check_legacy_setup_py_options(options, reqs)
+
+ if "no-binary-enable-wheel-cache" in options.features_enabled:
+ # TODO: remove format_control from WheelCache when the deprecation cycle
+ # is over
+ wheel_cache = WheelCache(options.cache_dir)
+ else:
+ if options.format_control.no_binary:
+ deprecated(
+ reason=(
+ "--no-binary currently disables reading from "
+ "the cache of locally built wheels. In the future "
+ "--no-binary will not influence the wheel cache."
+ ),
+ replacement="to use the --no-cache-dir option",
+ feature_flag="no-binary-enable-wheel-cache",
+ issue=11453,
+ gone_in="23.1",
+ )
+ wheel_cache = WheelCache(options.cache_dir, options.format_control)
preparer = self.make_requirement_preparer(
temp_build_dir=directory,
options=options,
- req_tracker=req_tracker,
+ build_tracker=build_tracker,
session=session,
finder=finder,
download_dir=options.wheel_dir,
use_user_site=False,
+ verbosity=self.verbosity,
)
resolver = self.make_resolver(
diff --git a/src/pip/_internal/configuration.py b/src/pip/_internal/configuration.py
index a8092d1ae..6cce8bcbc 100644
--- a/src/pip/_internal/configuration.py
+++ b/src/pip/_internal/configuration.py
@@ -36,12 +36,20 @@ ENV_NAMES_IGNORED = "version", "help"
kinds = enum(
USER="user", # User Specific
GLOBAL="global", # System Wide
- SITE="site", # [Virtual] Environment Specific
+ BASE="base", # Base environment specific (e.g. for all venvs with the same base)
+ SITE="site", # Environment Specific (e.g. per venv)
ENV="env", # from PIP_CONFIG_FILE
ENV_VAR="env-var", # from Environment Variables
)
-OVERRIDE_ORDER = kinds.GLOBAL, kinds.USER, kinds.SITE, kinds.ENV, kinds.ENV_VAR
-VALID_LOAD_ONLY = kinds.USER, kinds.GLOBAL, kinds.SITE
+OVERRIDE_ORDER = (
+ kinds.GLOBAL,
+ kinds.USER,
+ kinds.BASE,
+ kinds.SITE,
+ kinds.ENV,
+ kinds.ENV_VAR,
+)
+VALID_LOAD_ONLY = kinds.USER, kinds.GLOBAL, kinds.BASE, kinds.SITE
logger = getLogger(__name__)
@@ -70,6 +78,7 @@ def get_configuration_files() -> Dict[Kind, List[str]]:
os.path.join(path, CONFIG_BASENAME) for path in appdirs.site_config_dirs("pip")
]
+ base_config_file = os.path.join(sys.base_prefix, CONFIG_BASENAME)
site_config_file = os.path.join(sys.prefix, CONFIG_BASENAME)
legacy_config_file = os.path.join(
os.path.expanduser("~"),
@@ -78,6 +87,7 @@ def get_configuration_files() -> Dict[Kind, List[str]]:
)
new_config_file = os.path.join(appdirs.user_config_dir("pip"), CONFIG_BASENAME)
return {
+ kinds.BASE: [base_config_file],
kinds.GLOBAL: global_config_files,
kinds.SITE: [site_config_file],
kinds.USER: [legacy_config_file, new_config_file],
@@ -142,13 +152,19 @@ class Configuration:
def get_value(self, key: str) -> Any:
"""Get a value from the configuration."""
+ orig_key = key
+ key = _normalize_name(key)
try:
return self._dictionary[key]
except KeyError:
- raise ConfigurationError(f"No such key - {key}")
+ # disassembling triggers a more useful error message than simply
+ # "No such key" in the case that the key isn't in the form command.option
+ _disassemble_key(key)
+ raise ConfigurationError(f"No such key - {orig_key}")
def set_value(self, key: str, value: Any) -> None:
"""Modify a value in the configuration."""
+ key = _normalize_name(key)
self._ensure_have_load_only()
assert self.load_only
@@ -167,11 +183,13 @@ class Configuration:
def unset_value(self, key: str) -> None:
"""Unset a value in the configuration."""
+ orig_key = key
+ key = _normalize_name(key)
self._ensure_have_load_only()
assert self.load_only
if key not in self._config[self.load_only]:
- raise ConfigurationError(f"No such key - {key}")
+ raise ConfigurationError(f"No such key - {orig_key}")
fname, parser = self._get_parser_to_modify()
@@ -336,6 +354,8 @@ class Configuration:
# The legacy config file is overridden by the new config file
yield kinds.USER, config_files[kinds.USER]
+ yield kinds.BASE, config_files[kinds.BASE]
+
# finally virtualenv configuration first trumping others
yield kinds.SITE, config_files[kinds.SITE]
diff --git a/src/pip/_internal/distributions/base.py b/src/pip/_internal/distributions/base.py
index 149fff55d..75ce2dc90 100644
--- a/src/pip/_internal/distributions/base.py
+++ b/src/pip/_internal/distributions/base.py
@@ -31,6 +31,9 @@ class AbstractDistribution(metaclass=abc.ABCMeta):
@abc.abstractmethod
def prepare_distribution_metadata(
- self, finder: PackageFinder, build_isolation: bool
+ self,
+ finder: PackageFinder,
+ build_isolation: bool,
+ check_build_deps: bool,
) -> None:
raise NotImplementedError()
diff --git a/src/pip/_internal/distributions/installed.py b/src/pip/_internal/distributions/installed.py
index 6c8c17929..edb38aa1a 100644
--- a/src/pip/_internal/distributions/installed.py
+++ b/src/pip/_internal/distributions/installed.py
@@ -11,12 +11,13 @@ class InstalledDistribution(AbstractDistribution):
"""
def get_metadata_distribution(self) -> BaseDistribution:
- from pip._internal.metadata.pkg_resources import Distribution as _Dist
-
assert self.req.satisfied_by is not None, "not actually installed"
- return _Dist(self.req.satisfied_by)
+ return self.req.satisfied_by
def prepare_distribution_metadata(
- self, finder: PackageFinder, build_isolation: bool
+ self,
+ finder: PackageFinder,
+ build_isolation: bool,
+ check_build_deps: bool,
) -> None:
pass
diff --git a/src/pip/_internal/distributions/sdist.py b/src/pip/_internal/distributions/sdist.py
index cd85ac5c4..4c2564793 100644
--- a/src/pip/_internal/distributions/sdist.py
+++ b/src/pip/_internal/distributions/sdist.py
@@ -19,12 +19,13 @@ class SourceDistribution(AbstractDistribution):
"""
def get_metadata_distribution(self) -> BaseDistribution:
- from pip._internal.metadata.pkg_resources import Distribution as _Dist
-
- return _Dist(self.req.get_dist())
+ return self.req.get_dist()
def prepare_distribution_metadata(
- self, finder: PackageFinder, build_isolation: bool
+ self,
+ finder: PackageFinder,
+ build_isolation: bool,
+ check_build_deps: bool,
) -> None:
# Load pyproject.toml, to determine whether PEP 517 is to be used
self.req.load_pyproject_toml()
@@ -45,7 +46,18 @@ class SourceDistribution(AbstractDistribution):
self.req.isolated_editable_sanity_check()
# Install the dynamic build requirements.
self._install_build_reqs(finder)
-
+ # Check if the current environment provides build dependencies
+ should_check_deps = self.req.use_pep517 and check_build_deps
+ if should_check_deps:
+ pyproject_requires = self.req.pyproject_requires
+ assert pyproject_requires is not None
+ conflicting, missing = self.req.build_env.check_requirements(
+ pyproject_requires
+ )
+ if conflicting:
+ self._raise_conflicts("the backend dependencies", conflicting)
+ if missing:
+ self._raise_missing_reqs(missing)
self.req.prepare_metadata()
def _prepare_build_backend(self, finder: PackageFinder) -> None:
@@ -56,7 +68,7 @@ class SourceDistribution(AbstractDistribution):
self.req.build_env = BuildEnvironment()
self.req.build_env.install_requirements(
- finder, pyproject_requires, "overlay", "Installing build dependencies"
+ finder, pyproject_requires, "overlay", kind="build dependencies"
)
conflicting, missing = self.req.build_env.check_requirements(
self.req.requirements_to_check
@@ -108,7 +120,7 @@ class SourceDistribution(AbstractDistribution):
if conflicting:
self._raise_conflicts("the backend dependencies", conflicting)
self.req.build_env.install_requirements(
- finder, missing, "normal", "Installing backend dependencies"
+ finder, missing, "normal", kind="backend dependencies"
)
def _raise_conflicts(
@@ -127,3 +139,12 @@ class SourceDistribution(AbstractDistribution):
),
)
raise InstallationError(error_message)
+
+ def _raise_missing_reqs(self, missing: Set[str]) -> None:
+ format_string = (
+ "Some build dependencies for {requirement} are missing: {missing}."
+ )
+ error_message = format_string.format(
+ requirement=self.req, missing=", ".join(map(repr, sorted(missing)))
+ )
+ raise InstallationError(error_message)
diff --git a/src/pip/_internal/distributions/wheel.py b/src/pip/_internal/distributions/wheel.py
index 340b0f3c5..03aac775b 100644
--- a/src/pip/_internal/distributions/wheel.py
+++ b/src/pip/_internal/distributions/wheel.py
@@ -26,6 +26,9 @@ class WheelDistribution(AbstractDistribution):
return get_wheel_distribution(wheel, canonicalize_name(self.req.name))
def prepare_distribution_metadata(
- self, finder: PackageFinder, build_isolation: bool
+ self,
+ finder: PackageFinder,
+ build_isolation: bool,
+ check_build_deps: bool,
) -> None:
pass
diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py
index ef5bc7511..d4527295d 100644
--- a/src/pip/_internal/exceptions.py
+++ b/src/pip/_internal/exceptions.py
@@ -1,23 +1,181 @@
-"""Exceptions used throughout package"""
+"""Exceptions used throughout package.
+
+This module MUST NOT try to import from anything within `pip._internal` to
+operate. This is expected to be importable from any/all files within the
+subpackage and, thus, should not depend on them.
+"""
import configparser
+import contextlib
+import locale
+import logging
+import pathlib
+import re
+import sys
from itertools import chain, groupby, repeat
-from typing import TYPE_CHECKING, Dict, List, Optional, Union
+from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Union
-from pip._vendor.pkg_resources import Distribution
from pip._vendor.requests.models import Request, Response
+from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult
+from pip._vendor.rich.markup import escape
+from pip._vendor.rich.text import Text
if TYPE_CHECKING:
from hashlib import _Hash
+ from typing import Literal
from pip._internal.metadata import BaseDistribution
from pip._internal.req.req_install import InstallRequirement
+logger = logging.getLogger(__name__)
+
+
+#
+# Scaffolding
+#
+def _is_kebab_case(s: str) -> bool:
+ return re.match(r"^[a-z]+(-[a-z]+)*$", s) is not None
+
+
+def _prefix_with_indent(
+ s: Union[Text, str],
+ console: Console,
+ *,
+ prefix: str,
+ indent: str,
+) -> Text:
+ if isinstance(s, Text):
+ text = s
+ else:
+ text = console.render_str(s)
+
+ return console.render_str(prefix, overflow="ignore") + console.render_str(
+ f"\n{indent}", overflow="ignore"
+ ).join(text.split(allow_blank=True))
+
class PipError(Exception):
- """Base pip exception"""
+ """The base pip error."""
+
+
+class DiagnosticPipError(PipError):
+ """An error, that presents diagnostic information to the user.
+
+ This contains a bunch of logic, to enable pretty presentation of our error
+ messages. Each error gets a unique reference. Each error can also include
+ additional context, a hint and/or a note -- which are presented with the
+ main error message in a consistent style.
+
+ This is adapted from the error output styling in `sphinx-theme-builder`.
+ """
+
+ reference: str
+
+ def __init__(
+ self,
+ *,
+ kind: 'Literal["error", "warning"]' = "error",
+ reference: Optional[str] = None,
+ message: Union[str, Text],
+ context: Optional[Union[str, Text]],
+ hint_stmt: Optional[Union[str, Text]],
+ note_stmt: Optional[Union[str, Text]] = None,
+ link: Optional[str] = None,
+ ) -> None:
+ # Ensure a proper reference is provided.
+ if reference is None:
+ assert hasattr(self, "reference"), "error reference not provided!"
+ reference = self.reference
+ assert _is_kebab_case(reference), "error reference must be kebab-case!"
+
+ self.kind = kind
+ self.reference = reference
+
+ self.message = message
+ self.context = context
+
+ self.note_stmt = note_stmt
+ self.hint_stmt = hint_stmt
+
+ self.link = link
+
+ super().__init__(f"<{self.__class__.__name__}: {self.reference}>")
+ def __repr__(self) -> str:
+ return (
+ f"<{self.__class__.__name__}("
+ f"reference={self.reference!r}, "
+ f"message={self.message!r}, "
+ f"context={self.context!r}, "
+ f"note_stmt={self.note_stmt!r}, "
+ f"hint_stmt={self.hint_stmt!r}"
+ ")>"
+ )
+ def __rich_console__(
+ self,
+ console: Console,
+ options: ConsoleOptions,
+ ) -> RenderResult:
+ colour = "red" if self.kind == "error" else "yellow"
+
+ yield f"[{colour} bold]{self.kind}[/]: [bold]{self.reference}[/]"
+ yield ""
+
+ if not options.ascii_only:
+ # Present the main message, with relevant context indented.
+ if self.context is not None:
+ yield _prefix_with_indent(
+ self.message,
+ console,
+ prefix=f"[{colour}]×[/] ",
+ indent=f"[{colour}]│[/] ",
+ )
+ yield _prefix_with_indent(
+ self.context,
+ console,
+ prefix=f"[{colour}]╰─>[/] ",
+ indent=f"[{colour}] [/] ",
+ )
+ else:
+ yield _prefix_with_indent(
+ self.message,
+ console,
+ prefix="[red]×[/] ",
+ indent=" ",
+ )
+ else:
+ yield self.message
+ if self.context is not None:
+ yield ""
+ yield self.context
+
+ if self.note_stmt is not None or self.hint_stmt is not None:
+ yield ""
+
+ if self.note_stmt is not None:
+ yield _prefix_with_indent(
+ self.note_stmt,
+ console,
+ prefix="[magenta bold]note[/]: ",
+ indent=" ",
+ )
+ if self.hint_stmt is not None:
+ yield _prefix_with_indent(
+ self.hint_stmt,
+ console,
+ prefix="[cyan bold]hint[/]: ",
+ indent=" ",
+ )
+
+ if self.link is not None:
+ yield ""
+ yield f"Link: {self.link}"
+
+
+#
+# Actual Errors
+#
class ConfigurationError(PipError):
"""General exception in configuration"""
@@ -30,18 +188,52 @@ class UninstallationError(PipError):
"""General exception during uninstallation"""
+class MissingPyProjectBuildRequires(DiagnosticPipError):
+ """Raised when pyproject.toml has `build-system`, but no `build-system.requires`."""
+
+ reference = "missing-pyproject-build-system-requires"
+
+ def __init__(self, *, package: str) -> None:
+ super().__init__(
+ message=f"Can not process {escape(package)}",
+ context=Text(
+ "This package has an invalid pyproject.toml file.\n"
+ "The [build-system] table is missing the mandatory `requires` key."
+ ),
+ note_stmt="This is an issue with the package mentioned above, not pip.",
+ hint_stmt=Text("See PEP 518 for the detailed specification."),
+ )
+
+
+class InvalidPyProjectBuildRequires(DiagnosticPipError):
+ """Raised when pyproject.toml an invalid `build-system.requires`."""
+
+ reference = "invalid-pyproject-build-system-requires"
+
+ def __init__(self, *, package: str, reason: str) -> None:
+ super().__init__(
+ message=f"Can not process {escape(package)}",
+ context=Text(
+ "This package has an invalid `build-system.requires` key in "
+ f"pyproject.toml.\n{reason}"
+ ),
+ note_stmt="This is an issue with the package mentioned above, not pip.",
+ hint_stmt=Text("See PEP 518 for the detailed specification."),
+ )
+
+
class NoneMetadataError(PipError):
- """
- Raised when accessing "METADATA" or "PKG-INFO" metadata for a
- pip._vendor.pkg_resources.Distribution object and
- `dist.has_metadata('METADATA')` returns True but
- `dist.get_metadata('METADATA')` returns None (and similarly for
- "PKG-INFO").
+ """Raised when accessing a Distribution's "METADATA" or "PKG-INFO".
+
+ This signifies an inconsistency, when the Distribution claims to have
+ the metadata file (if not, raise ``FileNotFoundError`` instead), but is
+ not actually able to produce its content. This may be due to permission
+ errors.
"""
def __init__(
self,
- dist: Union[Distribution, "BaseDistribution"],
+ dist: "BaseDistribution",
metadata_name: str,
) -> None:
"""
@@ -103,7 +295,10 @@ class NetworkConnectionError(PipError):
"""HTTP connection error"""
def __init__(
- self, error_msg: str, response: Response = None, request: Request = None
+ self,
+ error_msg: str,
+ response: Optional[Response] = None,
+ request: Optional[Request] = None,
) -> None:
"""
Initialize NetworkConnectionError with `request` and `response`
@@ -132,12 +327,23 @@ class UnsupportedWheel(InstallationError):
"""Unsupported wheel."""
+class InvalidWheel(InstallationError):
+ """Invalid (e.g. corrupt) wheel."""
+
+ def __init__(self, location: str, name: str):
+ self.location = location
+ self.name = name
+
+ def __str__(self) -> str:
+ return f"Wheel '{self.name}' located at {self.location} is invalid."
+
+
class MetadataInconsistent(InstallationError):
"""Built metadata contains inconsistent information.
This is raised when the metadata contains values (e.g. name and version)
- that do not match the information previously obtained from sdist filename
- or user-supplied ``#egg=`` value.
+ that do not match the information previously obtained from sdist filename,
+ user-supplied ``#egg=`` value, or an install requirement name.
"""
def __init__(
@@ -149,25 +355,84 @@ class MetadataInconsistent(InstallationError):
self.m_val = m_val
def __str__(self) -> str:
- template = (
- "Requested {} has inconsistent {}: "
- "filename has {!r}, but metadata has {!r}"
+ return (
+ f"Requested {self.ireq} has inconsistent {self.field}: "
+ f"expected {self.f_val!r}, but metadata has {self.m_val!r}"
+ )
+
+
+class LegacyInstallFailure(DiagnosticPipError):
+ """Error occurred while executing `setup.py install`"""
+
+ reference = "legacy-install-failure"
+
+ def __init__(self, package_details: str) -> None:
+ super().__init__(
+ message="Encountered error while trying to install package.",
+ context=package_details,
+ hint_stmt="See above for output from the failure.",
+ note_stmt="This is an issue with the package mentioned above, not pip.",
)
- return template.format(self.ireq, self.field, self.f_val, self.m_val)
-class InstallationSubprocessError(InstallationError):
- """A subprocess call failed during installation."""
+class InstallationSubprocessError(DiagnosticPipError, InstallationError):
+ """A subprocess call failed."""
- def __init__(self, returncode: int, description: str) -> None:
- self.returncode = returncode
- self.description = description
+ reference = "subprocess-exited-with-error"
+
+ def __init__(
+ self,
+ *,
+ command_description: str,
+ exit_code: int,
+ output_lines: Optional[List[str]],
+ ) -> None:
+ if output_lines is None:
+ output_prompt = Text("See above for output.")
+ else:
+ output_prompt = (
+ Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n")
+ + Text("".join(output_lines))
+ + Text.from_markup(R"[red]\[end of output][/]")
+ )
+
+ super().__init__(
+ message=(
+ f"[green]{escape(command_description)}[/] did not run successfully.\n"
+ f"exit code: {exit_code}"
+ ),
+ context=output_prompt,
+ hint_stmt=None,
+ note_stmt=(
+ "This error originates from a subprocess, and is likely not a "
+ "problem with pip."
+ ),
+ )
+
+ self.command_description = command_description
+ self.exit_code = exit_code
def __str__(self) -> str:
- return (
- "Command errored out with exit status {}: {} "
- "Check the logs for full command output."
- ).format(self.returncode, self.description)
+ return f"{self.command_description} exited with {self.exit_code}"
+
+
+class MetadataGenerationFailed(InstallationSubprocessError, InstallationError):
+ reference = "metadata-generation-failed"
+
+ def __init__(
+ self,
+ *,
+ package_details: str,
+ ) -> None:
+ super(InstallationSubprocessError, self).__init__(
+ message="Encountered error while generating package metadata.",
+ context=escape(package_details),
+ hint_stmt="See above for details.",
+ note_stmt="This is an issue with the package mentioned above, not pip.",
+ )
+
+ def __str__(self) -> str:
+ return "metadata generation failed"
class HashErrors(InstallationError):
@@ -400,3 +665,83 @@ class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
assert self.error is not None
message_part = f".\n{self.error}\n"
return f"Configuration file {self.reason}{message_part}"
+
+
+_DEFAULT_EXTERNALLY_MANAGED_ERROR = f"""\
+The Python environment under {sys.prefix} is managed externally, and may not be
+manipulated by the user. Please use specific tooling from the distributor of
+the Python installation to interact with this environment instead.
+"""
+
+
+class ExternallyManagedEnvironment(DiagnosticPipError):
+ """The current environment is externally managed.
+
+ This is raised when the current environment is externally managed, as
+ defined by `PEP 668`_. The ``EXTERNALLY-MANAGED`` configuration is checked
+ and displayed when the error is bubbled up to the user.
+
+ :param error: The error message read from ``EXTERNALLY-MANAGED``.
+ """
+
+ reference = "externally-managed-environment"
+
+ def __init__(self, error: Optional[str]) -> None:
+ if error is None:
+ context = Text(_DEFAULT_EXTERNALLY_MANAGED_ERROR)
+ else:
+ context = Text(error)
+ super().__init__(
+ message="This environment is externally managed",
+ context=context,
+ note_stmt=(
+ "If you believe this is a mistake, please contact your "
+ "Python installation or OS distribution provider. "
+ "You can override this, at the risk of breaking your Python "
+ "installation or OS, by passing --break-system-packages."
+ ),
+ hint_stmt=Text("See PEP 668 for the detailed specification."),
+ )
+
+ @staticmethod
+ def _iter_externally_managed_error_keys() -> Iterator[str]:
+ # LC_MESSAGES is in POSIX, but not the C standard. The most common
+ # platform that does not implement this category is Windows, where
+ # using other categories for console message localization is equally
+ # unreliable, so we fall back to the locale-less vendor message. This
+ # can always be re-evaluated when a vendor proposes a new alternative.
+ try:
+ category = locale.LC_MESSAGES
+ except AttributeError:
+ lang: Optional[str] = None
+ else:
+ lang, _ = locale.getlocale(category)
+ if lang is not None:
+ yield f"Error-{lang}"
+ for sep in ("-", "_"):
+ before, found, _ = lang.partition(sep)
+ if not found:
+ continue
+ yield f"Error-{before}"
+ yield "Error"
+
+ @classmethod
+ def from_config(
+ cls,
+ config: Union[pathlib.Path, str],
+ ) -> "ExternallyManagedEnvironment":
+ parser = configparser.ConfigParser(interpolation=None)
+ try:
+ parser.read(config, encoding="utf-8")
+ section = parser["externally-managed"]
+ for key in cls._iter_externally_managed_error_keys():
+ with contextlib.suppress(KeyError):
+ return cls(section[key])
+ except KeyError:
+ pass
+ except (OSError, UnicodeDecodeError, configparser.ParsingError):
+ from pip._internal.utils._log import VERBOSE
+
+ exc_info = logger.isEnabledFor(VERBOSE)
+ logger.warning("Failed to read %s", config, exc_info=exc_info)
+ return cls(None)
diff --git a/src/pip/_internal/index/collector.py b/src/pip/_internal/index/collector.py
index d9412234e..b3e293ea3 100644
--- a/src/pip/_internal/index/collector.py
+++ b/src/pip/_internal/index/collector.py
@@ -2,29 +2,32 @@
The main purpose of this module is to expose LinkCollector.collect_sources().
"""
-import cgi
import collections
+import email.message
import functools
import itertools
+import json
import logging
import os
-import re
import urllib.parse
import urllib.request
-import xml.etree.ElementTree
+from html.parser import HTMLParser
from optparse import Values
from typing import (
+ TYPE_CHECKING,
Callable,
+ Dict,
Iterable,
List,
MutableMapping,
NamedTuple,
Optional,
Sequence,
+ Tuple,
Union,
)
-from pip._vendor import html5lib, requests
+from pip._vendor import requests
from pip._vendor.requests import Response
from pip._vendor.requests.exceptions import RetryError, SSLError
@@ -34,14 +37,18 @@ from pip._internal.models.search_scope import SearchScope
from pip._internal.network.session import PipSession
from pip._internal.network.utils import raise_for_status
from pip._internal.utils.filetypes import is_archive_file
-from pip._internal.utils.misc import pairwise, redact_auth_from_url
+from pip._internal.utils.misc import redact_auth_from_url
from pip._internal.vcs import vcs
from .sources import CandidatesFromPage, LinkSource, build_source
+if TYPE_CHECKING:
+ from typing import Protocol
+else:
+ Protocol = object
+
logger = logging.getLogger(__name__)
-HTMLElement = xml.etree.ElementTree.Element
ResponseHeaders = MutableMapping[str, str]
@@ -56,32 +63,46 @@ def _match_vcs_scheme(url: str) -> Optional[str]:
return None
-class _NotHTML(Exception):
+class _NotAPIContent(Exception):
def __init__(self, content_type: str, request_desc: str) -> None:
super().__init__(content_type, request_desc)
self.content_type = content_type
self.request_desc = request_desc
-def _ensure_html_header(response: Response) -> None:
- """Check the Content-Type header to ensure the response contains HTML.
+def _ensure_api_header(response: Response) -> None:
+ """
+ Check the Content-Type header to ensure the response contains a Simple
+ API Response.
- Raises `_NotHTML` if the content type is not text/html.
+ Raises `_NotAPIContent` if the content type is not a valid content-type.
"""
- content_type = response.headers.get("Content-Type", "")
- if not content_type.lower().startswith("text/html"):
- raise _NotHTML(content_type, response.request.method)
+ content_type = response.headers.get("Content-Type", "Unknown")
+
+ content_type_l = content_type.lower()
+ if content_type_l.startswith(
+ (
+ "text/html",
+ "application/vnd.pypi.simple.v1+html",
+ "application/vnd.pypi.simple.v1+json",
+ )
+ ):
+ return
+
+ raise _NotAPIContent(content_type, response.request.method)
class _NotHTTP(Exception):
pass
-def _ensure_html_response(url: str, session: PipSession) -> None:
- """Send a HEAD request to the URL, and ensure the response contains HTML.
+def _ensure_api_response(url: str, session: PipSession) -> None:
+ """
+ Send a HEAD request to the URL, and ensure the response contains a simple
+ API Response.
Raises `_NotHTTP` if the URL is not available for a HEAD request, or
- `_NotHTML` if the content type is not text/html.
+ `_NotAPIContent` if the content type is not a valid content type.
"""
scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url)
if scheme not in {"http", "https"}:
@@ -90,31 +111,37 @@ def _ensure_html_response(url: str, session: PipSession) -> None:
resp = session.head(url, allow_redirects=True)
raise_for_status(resp)
- _ensure_html_header(resp)
+ _ensure_api_header(resp)
-def _get_html_response(url: str, session: PipSession) -> Response:
- """Access an HTML page with GET, and return the response.
+def _get_simple_response(url: str, session: PipSession) -> Response:
+ """Access an Simple API response with GET, and return the response.
This consists of three parts:
1. If the URL looks suspiciously like an archive, send a HEAD first to
- check the Content-Type is HTML, to avoid downloading a large file.
- Raise `_NotHTTP` if the content type cannot be determined, or
- `_NotHTML` if it is not HTML.
+ check the Content-Type is HTML or Simple API, to avoid downloading a
+ large file. Raise `_NotHTTP` if the content type cannot be determined, or
+ `_NotAPIContent` if it is not HTML or a Simple API.
2. Actually perform the request. Raise HTTP exceptions on network failures.
- 3. Check the Content-Type header to make sure we got HTML, and raise
- `_NotHTML` otherwise.
+ 3. Check the Content-Type header to make sure we got a Simple API response,
+ and raise `_NotAPIContent` otherwise.
"""
if is_archive_file(Link(url).filename):
- _ensure_html_response(url, session=session)
+ _ensure_api_response(url, session=session)
logger.debug("Getting page %s", redact_auth_from_url(url))
resp = session.get(
url,
headers={
- "Accept": "text/html",
+ "Accept": ", ".join(
+ [
+ "application/vnd.pypi.simple.v1+json",
+ "application/vnd.pypi.simple.v1+html; q=0.1",
+ "text/html; q=0.01",
+ ]
+ ),
# We don't want to blindly returned cached data for
# /simple/, because authors generally expecting that
# twine upload && pip install will function, but if
@@ -136,9 +163,16 @@ def _get_html_response(url: str, session: PipSession) -> Response:
# The check for archives above only works if the url ends with
# something that looks like an archive. However that is not a
# requirement of an url. Unless we issue a HEAD request on every
- # url we cannot know ahead of time for sure if something is HTML
- # or not. However we can check after we've downloaded it.
- _ensure_html_header(resp)
+ # url we cannot know ahead of time for sure if something is a
+ # Simple API response or not. However we can check after we've
+ # downloaded it.
+ _ensure_api_header(resp)
+
+ logger.debug(
+ "Fetched page %s as %s",
+ redact_auth_from_url(url),
+ resp.headers.get("Content-Type", "Unknown"),
+ )
return resp
@@ -146,121 +180,16 @@ def _get_html_response(url: str, session: PipSession) -> Response:
def _get_encoding_from_headers(headers: ResponseHeaders) -> Optional[str]:
"""Determine if we have any encoding information in our headers."""
if headers and "Content-Type" in headers:
- content_type, params = cgi.parse_header(headers["Content-Type"])
- if "charset" in params:
- return params["charset"]
+ m = email.message.Message()
+ m["content-type"] = headers["Content-Type"]
+ charset = m.get_param("charset")
+ if charset:
+ return str(charset)
return None
-def _determine_base_url(document: HTMLElement, page_url: str) -> str:
- """Determine the HTML document's base URL.
-
- This looks for a ``<base>`` tag in the HTML document. If present, its href
- attribute denotes the base URL of anchor tags in the document. If there is
- no such tag (or if it does not have a valid href attribute), the HTML
- file's URL is used as the base URL.
-
- :param document: An HTML document representation. The current
- implementation expects the result of ``html5lib.parse()``.
- :param page_url: The URL of the HTML document.
- """
- for base in document.findall(".//base"):
- href = base.get("href")
- if href is not None:
- return href
- return page_url
-
-
-def _clean_url_path_part(part: str) -> str:
- """
- Clean a "part" of a URL path (i.e. after splitting on "@" characters).
- """
- # We unquote prior to quoting to make sure nothing is double quoted.
- return urllib.parse.quote(urllib.parse.unquote(part))
-
-
-def _clean_file_url_path(part: str) -> str:
- """
- Clean the first part of a URL path that corresponds to a local
- filesystem path (i.e. the first part after splitting on "@" characters).
- """
- # We unquote prior to quoting to make sure nothing is double quoted.
- # Also, on Windows the path part might contain a drive letter which
- # should not be quoted. On Linux where drive letters do not
- # exist, the colon should be quoted. We rely on urllib.request
- # to do the right thing here.
- return urllib.request.pathname2url(urllib.request.url2pathname(part))
-
-
-# percent-encoded: /
-_reserved_chars_re = re.compile("(@|%2F)", re.IGNORECASE)
-
-
-def _clean_url_path(path: str, is_local_path: bool) -> str:
- """
- Clean the path portion of a URL.
- """
- if is_local_path:
- clean_func = _clean_file_url_path
- else:
- clean_func = _clean_url_path_part
-
- # Split on the reserved characters prior to cleaning so that
- # revision strings in VCS URLs are properly preserved.
- parts = _reserved_chars_re.split(path)
-
- cleaned_parts = []
- for to_clean, reserved in pairwise(itertools.chain(parts, [""])):
- cleaned_parts.append(clean_func(to_clean))
- # Normalize %xx escapes (e.g. %2f -> %2F)
- cleaned_parts.append(reserved.upper())
-
- return "".join(cleaned_parts)
-
-
-def _clean_link(url: str) -> str:
- """
- Make sure a link is fully quoted.
- For example, if ' ' occurs in the URL, it will be replaced with "%20",
- and without double-quoting other characters.
- """
- # Split the URL into parts according to the general structure
- # `scheme://netloc/path;parameters?query#fragment`.
- result = urllib.parse.urlparse(url)
- # If the netloc is empty, then the URL refers to a local filesystem path.
- is_local_path = not result.netloc
- path = _clean_url_path(result.path, is_local_path=is_local_path)
- return urllib.parse.urlunparse(result._replace(path=path))
-
-
-def _create_link_from_element(
- anchor: HTMLElement,
- page_url: str,
- base_url: str,
-) -> Optional[Link]:
- """
- Convert an anchor element in a simple repository page to a Link.
- """
- href = anchor.get("href")
- if not href:
- return None
-
- url = _clean_link(urllib.parse.urljoin(base_url, href))
- pyrequire = anchor.get("data-requires-python")
- yanked_reason = anchor.get("data-yanked")
-
- link = Link(
- url,
- comes_from=page_url,
- requires_python=pyrequire,
- yanked_reason=yanked_reason,
- )
-
- return link
-
-
class CacheablePageContent:
- def __init__(self, page: "HTMLPage") -> None:
+ def __init__(self, page: "IndexContent") -> None:
assert page.cache_link_parsing
self.page = page
@@ -271,12 +200,15 @@ class CacheablePageContent:
return hash(self.page.url)
-def with_cached_html_pages(
- fn: Callable[["HTMLPage"], Iterable[Link]],
-) -> Callable[["HTMLPage"], List[Link]]:
+class ParseLinks(Protocol):
+ def __call__(self, page: "IndexContent") -> Iterable[Link]:
+ ...
+
+
+def with_cached_index_content(fn: ParseLinks) -> ParseLinks:
"""
- Given a function that parses an Iterable[Link] from an HTMLPage, cache the
- function's result (keyed by CacheablePageContent), unless the HTMLPage
+ Given a function that parses an Iterable[Link] from an IndexContent, cache the
+ function's result (keyed by CacheablePageContent), unless the IndexContent
`page` has `page.cache_link_parsing == False`.
"""
@@ -285,7 +217,7 @@ def with_cached_html_pages(
return list(fn(cacheable_page.page))
@functools.wraps(fn)
- def wrapper_wrapper(page: "HTMLPage") -> List[Link]:
+ def wrapper_wrapper(page: "IndexContent") -> List[Link]:
if page.cache_link_parsing:
return wrapper(CacheablePageContent(page))
return list(fn(page))
@@ -293,36 +225,42 @@ def with_cached_html_pages(
return wrapper_wrapper
-@with_cached_html_pages
-def parse_links(page: "HTMLPage") -> Iterable[Link]:
+@with_cached_index_content
+def parse_links(page: "IndexContent") -> Iterable[Link]:
"""
- Parse an HTML document, and yield its anchor elements as Link objects.
+ Parse a Simple API's Index Content, and yield its anchor elements as Link objects.
"""
- document = html5lib.parse(
- page.content,
- transport_encoding=page.encoding,
- namespaceHTMLElements=False,
- )
+
+ content_type_l = page.content_type.lower()
+ if content_type_l.startswith("application/vnd.pypi.simple.v1+json"):
+ data = json.loads(page.content)
+ for file in data.get("files", []):
+ link = Link.from_json(file, page.url)
+ if link is None:
+ continue
+ yield link
+ return
+
+ parser = HTMLLinkParser(page.url)
+ encoding = page.encoding or "utf-8"
+ parser.feed(page.content.decode(encoding))
url = page.url
- base_url = _determine_base_url(document, url)
- for anchor in document.findall(".//a"):
- link = _create_link_from_element(
- anchor,
- page_url=url,
- base_url=base_url,
- )
+ base_url = parser.base_url or url
+ for anchor in parser.anchors:
+ link = Link.from_element(anchor, page_url=url, base_url=base_url)
if link is None:
continue
yield link
-class HTMLPage:
- """Represents one page, along with its URL"""
+class IndexContent:
+ """Represents one response (or page), along with its URL"""
def __init__(
self,
content: bytes,
+ content_type: str,
encoding: Optional[str],
url: str,
cache_link_parsing: bool = True,
@@ -335,6 +273,7 @@ class HTMLPage:
have this set to False, for example.
"""
self.content = content
+ self.content_type = content_type
self.encoding = encoding
self.url = url
self.cache_link_parsing = cache_link_parsing
@@ -343,7 +282,35 @@ class HTMLPage:
return redact_auth_from_url(self.url)
-def _handle_get_page_fail(
+class HTMLLinkParser(HTMLParser):
+ """
+ HTMLParser that keeps the first base HREF and a list of all anchor
+ elements' attributes.
+ """
+
+ def __init__(self, url: str) -> None:
+ super().__init__(convert_charrefs=True)
+
+ self.url: str = url
+ self.base_url: Optional[str] = None
+ self.anchors: List[Dict[str, Optional[str]]] = []
+
+ def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> None:
+ if tag == "base" and self.base_url is None:
+ href = self.get_href(attrs)
+ if href is not None:
+ self.base_url = href
+ elif tag == "a":
+ self.anchors.append(dict(attrs))
+
+ def get_href(self, attrs: List[Tuple[str, Optional[str]]]) -> Optional[str]:
+ for name, value in attrs:
+ if name == "href":
+ return value
+ return None
+
+
+def _handle_get_simple_fail(
link: Link,
reason: Union[str, Exception],
meth: Optional[Callable[..., None]] = None,
@@ -353,24 +320,20 @@ def _handle_get_page_fail(
meth("Could not fetch URL %s: %s - skipping", link, reason)
-def _make_html_page(response: Response, cache_link_parsing: bool = True) -> HTMLPage:
+def _make_index_content(
+ response: Response, cache_link_parsing: bool = True
+) -> IndexContent:
encoding = _get_encoding_from_headers(response.headers)
- return HTMLPage(
+ return IndexContent(
response.content,
+ response.headers["Content-Type"],
encoding=encoding,
url=response.url,
cache_link_parsing=cache_link_parsing,
)
-def _get_html_page(
- link: Link, session: Optional[PipSession] = None
-) -> Optional["HTMLPage"]:
- if session is None:
- raise TypeError(
- "_get_html_page() missing 1 required keyword argument: 'session'"
- )
-
+def _get_index_content(link: Link, *, session: PipSession) -> Optional["IndexContent"]:
url = link.url.split("#", 1)[0]
# Check for VCS schemes that do not support lookup as web pages.
@@ -390,39 +353,44 @@ def _get_html_page(
# final segment
if not url.endswith("/"):
url += "/"
+ # TODO: In the future, it would be nice if pip supported PEP 691
+ # style responses in the file:// URLs, however there's no
+ # standard file extension for application/vnd.pypi.simple.v1+json
+ # so we'll need to come up with something on our own.
url = urllib.parse.urljoin(url, "index.html")
logger.debug(" file: URL is directory, getting %s", url)
try:
- resp = _get_html_response(url, session=session)
+ resp = _get_simple_response(url, session=session)
except _NotHTTP:
logger.warning(
"Skipping page %s because it looks like an archive, and cannot "
"be checked by a HTTP HEAD request.",
link,
)
- except _NotHTML as exc:
+ except _NotAPIContent as exc:
logger.warning(
- "Skipping page %s because the %s request got Content-Type: %s."
- "The only supported Content-Type is text/html",
+ "Skipping page %s because the %s request got Content-Type: %s. "
+ "The only supported Content-Types are application/vnd.pypi.simple.v1+json, "
+ "application/vnd.pypi.simple.v1+html, and text/html",
link,
exc.request_desc,
exc.content_type,
)
except NetworkConnectionError as exc:
- _handle_get_page_fail(link, exc)
+ _handle_get_simple_fail(link, exc)
except RetryError as exc:
- _handle_get_page_fail(link, exc)
+ _handle_get_simple_fail(link, exc)
except SSLError as exc:
reason = "There was a problem confirming the ssl certificate: "
reason += str(exc)
- _handle_get_page_fail(link, reason, meth=logger.info)
+ _handle_get_simple_fail(link, reason, meth=logger.info)
except requests.ConnectionError as exc:
- _handle_get_page_fail(link, f"connection error: {exc}")
+ _handle_get_simple_fail(link, f"connection error: {exc}")
except requests.Timeout:
- _handle_get_page_fail(link, "timed out")
+ _handle_get_simple_fail(link, "timed out")
else:
- return _make_html_page(resp, cache_link_parsing=link.cache_link_parsing)
+ return _make_index_content(resp, cache_link_parsing=link.cache_link_parsing)
return None
@@ -474,6 +442,7 @@ class LinkCollector:
search_scope = SearchScope.create(
find_links=find_links,
index_urls=index_urls,
+ no_index=options.no_index,
)
link_collector = LinkCollector(
session=session,
@@ -485,11 +454,11 @@ class LinkCollector:
def find_links(self) -> List[str]:
return self.search_scope.find_links
- def fetch_page(self, location: Link) -> Optional[HTMLPage]:
+ def fetch_response(self, location: Link) -> Optional[IndexContent]:
"""
Fetch an HTML page containing package links.
"""
- return _get_html_page(location, session=self.session)
+ return _get_index_content(location, session=self.session)
def collect_sources(
self,
diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py
index a2702db7d..b6f8d57e8 100644
--- a/src/pip/_internal/index/package_finder.py
+++ b/src/pip/_internal/index/package_finder.py
@@ -1,13 +1,11 @@
"""Routines related to PyPI, indexes"""
-# The following comment should be removed at some point in the future.
-# mypy: strict-optional=False
-
+import enum
import functools
import itertools
import logging
import re
-from typing import FrozenSet, Iterable, List, Optional, Set, Tuple, Union
+from typing import TYPE_CHECKING, FrozenSet, Iterable, List, Optional, Set, Tuple, Union
from pip._vendor.packaging import specifiers
from pip._vendor.packaging.tags import Tag
@@ -37,7 +35,9 @@ from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import build_netloc
from pip._internal.utils.packaging import check_requires_python
from pip._internal.utils.unpacking import SUPPORTED_EXTENSIONS
-from pip._internal.utils.urls import url_to_path
+
+if TYPE_CHECKING:
+ from pip._vendor.typing_extensions import TypeGuard
__all__ = ["FormatControl", "BestCandidateResult", "PackageFinder"]
@@ -95,6 +95,16 @@ def _check_link_requires_python(
return True
+class LinkType(enum.Enum):
+ candidate = enum.auto()
+ different_project = enum.auto()
+ yanked = enum.auto()
+ format_unsupported = enum.auto()
+ format_invalid = enum.auto()
+ platform_mismatch = enum.auto()
+ requires_python_mismatch = enum.auto()
+
+
class LinkEvaluator:
"""
@@ -144,19 +154,20 @@ class LinkEvaluator:
self.project_name = project_name
- def evaluate_link(self, link: Link) -> Tuple[bool, Optional[str]]:
+ def evaluate_link(self, link: Link) -> Tuple[LinkType, str]:
"""
Determine whether a link is a candidate for installation.
- :return: A tuple (is_candidate, result), where `result` is (1) a
- version string if `is_candidate` is True, and (2) if
- `is_candidate` is False, an optional string to log the reason
- the link fails to qualify.
+ :return: A tuple (result, detail), where *result* is an enum
+ representing whether the evaluation found a candidate, or the reason
+ why one is not found. If a candidate is found, *detail* will be the
+ candidate's version string; if one is not found, it contains the
+ reason the link fails to qualify.
"""
version = None
if link.is_yanked and not self._allow_yanked:
reason = link.yanked_reason or "<none given>"
- return (False, f"yanked for reason: {reason}")
+ return (LinkType.yanked, f"yanked for reason: {reason}")
if link.egg_fragment:
egg_info = link.egg_fragment
@@ -164,42 +175,46 @@ class LinkEvaluator:
else:
egg_info, ext = link.splitext()
if not ext:
- return (False, "not a file")
+ return (LinkType.format_unsupported, "not a file")
if ext not in SUPPORTED_EXTENSIONS:
- return (False, f"unsupported archive format: {ext}")
+ return (
+ LinkType.format_unsupported,
+ f"unsupported archive format: {ext}",
+ )
if "binary" not in self._formats and ext == WHEEL_EXTENSION:
- reason = "No binaries permitted for {}".format(self.project_name)
- return (False, reason)
+ reason = f"No binaries permitted for {self.project_name}"
+ return (LinkType.format_unsupported, reason)
if "macosx10" in link.path and ext == ".zip":
- return (False, "macosx10 one")
+ return (LinkType.format_unsupported, "macosx10 one")
if ext == WHEEL_EXTENSION:
try:
wheel = Wheel(link.filename)
except InvalidWheelFilename:
- return (False, "invalid wheel filename")
+ return (
+ LinkType.format_invalid,
+ "invalid wheel filename",
+ )
if canonicalize_name(wheel.name) != self._canonical_name:
- reason = "wrong project name (not {})".format(self.project_name)
- return (False, reason)
+ reason = f"wrong project name (not {self.project_name})"
+ return (LinkType.different_project, reason)
supported_tags = self._target_python.get_tags()
if not wheel.supported(supported_tags):
# Include the wheel's tags in the reason string to
# simplify troubleshooting compatibility issues.
- file_tags = wheel.get_formatted_file_tags()
+ file_tags = ", ".join(wheel.get_formatted_file_tags())
reason = (
- "none of the wheel's tags ({}) are compatible "
- "(run pip debug --verbose to show compatible tags)".format(
- ", ".join(file_tags)
- )
+ f"none of the wheel's tags ({file_tags}) are compatible "
+ f"(run pip debug --verbose to show compatible tags)"
)
- return (False, reason)
+ return (LinkType.platform_mismatch, reason)
version = wheel.version
# This should be up by the self.ok_binary check, but see issue 2700.
if "source" not in self._formats and ext != WHEEL_EXTENSION:
reason = f"No sources permitted for {self.project_name}"
- return (False, reason)
+ return (LinkType.format_unsupported, reason)
if not version:
version = _extract_version_from_fragment(
@@ -208,14 +223,17 @@ class LinkEvaluator:
)
if not version:
reason = f"Missing project version for {self.project_name}"
- return (False, reason)
+ return (LinkType.format_invalid, reason)
match = self._py_version_re.search(version)
if match:
version = version[: match.start()]
py_version = match.group(1)
if py_version != self._target_python.py_version:
- return (False, "Python version is incorrect")
+ return (
+ LinkType.platform_mismatch,
+ "Python version is incorrect",
+ )
supports_python = _check_link_requires_python(
link,
@@ -223,18 +241,17 @@ class LinkEvaluator:
ignore_requires_python=self._ignore_requires_python,
)
if not supports_python:
- # Return None for the reason text to suppress calling
- # _log_skipped_link().
- return (False, None)
+ reason = f"{version} Requires-Python {link.requires_python}"
+ return (LinkType.requires_python_mismatch, reason)
logger.debug("Found link %s, version: %s", link, version)
- return (True, version)
+ return (LinkType.candidate, version)
def filter_unallowed_hashes(
candidates: List[InstallationCandidate],
- hashes: Hashes,
+ hashes: Optional[Hashes],
project_name: str,
) -> List[InstallationCandidate]:
"""
@@ -523,6 +540,7 @@ class CandidateEvaluator:
binary_preference = 1
if wheel.build_tag is not None:
match = re.match(r"^(\d+)(.*)$", wheel.build_tag)
+ assert match is not None, "guaranteed by filename validation"
build_tag_groups = match.groups()
build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
else: # sdist
@@ -609,7 +627,7 @@ class PackageFinder:
self.format_control = format_control
# These are boring links that have already been logged somehow.
- self._logged_links: Set[Link] = set()
+ self._logged_links: Set[Tuple[Link, LinkType, str]] = set()
# Don't include an allow_yanked default value to make sure each call
# site considers whether yanked releases are allowed. This also causes
@@ -686,6 +704,14 @@ class PackageFinder:
def set_prefer_binary(self) -> None:
self._candidate_prefs.prefer_binary = True
+ def requires_python_skipped_reasons(self) -> List[str]:
+ reasons = {
+ detail
+ for _, result, detail in self._logged_links
+ if result == LinkType.requires_python_mismatch
+ }
+ return sorted(reasons)
+
def make_link_evaluator(self, project_name: str) -> LinkEvaluator:
canonical_name = canonicalize_name(project_name)
formats = self.format_control.get_allowed_formats(canonical_name)
@@ -715,12 +741,13 @@ class PackageFinder:
no_eggs.append(link)
return no_eggs + eggs
- def _log_skipped_link(self, link: Link, reason: str) -> None:
- if link not in self._logged_links:
+ def _log_skipped_link(self, link: Link, result: LinkType, detail: str) -> None:
+ entry = (link, result, detail)
+ if entry not in self._logged_links:
# Put the link at the end so the reason is more visible and because
# the link string is usually very long.
- logger.debug("Skipping link: %s: %s", reason, link)
- self._logged_links.add(link)
+ logger.debug("Skipping link: %s: %s", detail, link)
+ self._logged_links.add(entry)
def get_install_candidate(
self, link_evaluator: LinkEvaluator, link: Link
@@ -729,16 +756,15 @@ class PackageFinder:
If the link is a candidate for install, convert it to an
InstallationCandidate and return it. Otherwise, return None.
"""
- is_candidate, result = link_evaluator.evaluate_link(link)
- if not is_candidate:
- if result:
- self._log_skipped_link(link, reason=result)
+ result, detail = link_evaluator.evaluate_link(link)
+ if result != LinkType.candidate:
+ self._log_skipped_link(link, result, detail)
return None
return InstallationCandidate(
name=link_evaluator.project_name,
link=link,
- version=result,
+ version=detail,
)
def evaluate_links(
@@ -762,11 +788,11 @@ class PackageFinder:
"Fetching project page and analyzing links: %s",
project_url,
)
- html_page = self._link_collector.fetch_page(project_url)
- if html_page is None:
+ index_response = self._link_collector.fetch_response(project_url)
+ if index_response is None:
return []
- page_links = list(parse_links(html_page))
+ page_links = list(parse_links(index_response))
with indent_log():
package_links = self.evaluate_links(
@@ -816,7 +842,14 @@ class PackageFinder:
)
if logger.isEnabledFor(logging.DEBUG) and file_candidates:
- paths = [url_to_path(c.link.url) for c in file_candidates]
+ paths = []
+ for candidate in file_candidates:
+ assert candidate.link.url # we need to have a URL
+ try:
+ paths.append(candidate.link.file_path)
+ except Exception:
+ paths.append(candidate.link.url) # it's not a local file
+
logger.debug("Local files found: %s", ", ".join(paths))
# This is an intentional priority ordering
@@ -881,7 +914,7 @@ class PackageFinder:
installed_version: Optional[_BaseVersion] = None
if req.satisfied_by is not None:
- installed_version = parse_version(req.satisfied_by.version)
+ installed_version = req.satisfied_by.version
def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str:
# This repeated parse_version and str() conversion is needed to
@@ -910,43 +943,46 @@ class PackageFinder:
"No matching distribution found for {}".format(req)
)
- best_installed = False
- if installed_version and (
- best_candidate is None or best_candidate.version <= installed_version
- ):
- best_installed = True
+ def _should_install_candidate(
+ candidate: Optional[InstallationCandidate],
+ ) -> "TypeGuard[InstallationCandidate]":
+ if installed_version is None:
+ return True
+ if best_candidate is None:
+ return False
+ return best_candidate.version > installed_version
if not upgrade and installed_version is not None:
- if best_installed:
+ if _should_install_candidate(best_candidate):
logger.debug(
- "Existing installed version (%s) is most up-to-date and "
- "satisfies requirement",
+ "Existing installed version (%s) satisfies requirement "
+ "(most up-to-date version is %s)",
installed_version,
+ best_candidate.version,
)
else:
logger.debug(
- "Existing installed version (%s) satisfies requirement "
- "(most up-to-date version is %s)",
+ "Existing installed version (%s) is most up-to-date and "
+ "satisfies requirement",
installed_version,
- best_candidate.version,
)
return None
- if best_installed:
- # We have an existing version, and its the best version
+ if _should_install_candidate(best_candidate):
logger.debug(
- "Installed version (%s) is most up-to-date (past versions: %s)",
- installed_version,
+ "Using version %s (newest of versions: %s)",
+ best_candidate.version,
_format_versions(best_candidate_result.iter_applicable()),
)
- raise BestVersionAlreadyInstalled
+ return best_candidate
+ # We have an existing version, and its the best version
logger.debug(
- "Using version %s (newest of versions: %s)",
- best_candidate.version,
+ "Installed version (%s) is most up-to-date (past versions: %s)",
+ installed_version,
_format_versions(best_candidate_result.iter_applicable()),
)
- return best_candidate
+ raise BestVersionAlreadyInstalled
def _find_name_version_sep(fragment: str, canonical_name: str) -> int:
diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py
index dba182d75..d54bc63eb 100644
--- a/src/pip/_internal/locations/__init__.py
+++ b/src/pip/_internal/locations/__init__.py
@@ -4,14 +4,14 @@ import os
import pathlib
import sys
import sysconfig
-from typing import Any, Dict, Iterator, List, Optional, Tuple
+from typing import Any, Dict, Generator, Optional, Tuple
from pip._internal.models.scheme import SCHEME_KEYS, Scheme
from pip._internal.utils.compat import WINDOWS
from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.virtualenv import running_under_virtualenv
-from . import _distutils, _sysconfig
+from . import _sysconfig
from .base import (
USER_CACHE_DIR,
get_major_minor_version,
@@ -27,7 +27,6 @@ __all__ = [
"get_bin_user",
"get_major_minor_version",
"get_platlib",
- "get_prefixed_libs",
"get_purelib",
"get_scheme",
"get_src_prefix",
@@ -38,14 +37,40 @@ __all__ = [
logger = logging.getLogger(__name__)
-if os.environ.get("_PIP_LOCATIONS_NO_WARN_ON_MISMATCH"):
- _MISMATCH_LEVEL = logging.DEBUG
-else:
- _MISMATCH_LEVEL = logging.WARNING
_PLATLIBDIR: str = getattr(sys, "platlibdir", "lib")
-_USE_SYSCONFIG = sys.version_info >= (3, 10)
+_USE_SYSCONFIG_DEFAULT = sys.version_info >= (3, 10)
+
+
+def _should_use_sysconfig() -> bool:
+ """This function determines the value of _USE_SYSCONFIG.
+
+ By default, pip uses sysconfig on Python 3.10+.
+ But Python distributors can override this decision by setting:
+ sysconfig._PIP_USE_SYSCONFIG = True / False
+ Rationale in https://github.com/pypa/pip/issues/10647
+
+ This is a function for testability, but should be constant during any one
+ run.
+ """
+ return bool(getattr(sysconfig, "_PIP_USE_SYSCONFIG", _USE_SYSCONFIG_DEFAULT))
+
+
+_USE_SYSCONFIG = _should_use_sysconfig()
+
+if not _USE_SYSCONFIG:
+ # Import distutils lazily to avoid deprecation warnings,
+ # but import it soon enough that it is in memory and available during
+ # a pip reinstall.
+ from . import _distutils
+
+# Be noisy about incompatibilities if this platforms "should" be using
+# sysconfig, but is explicitly opting out and using distutils instead.
+if _USE_SYSCONFIG_DEFAULT and not _USE_SYSCONFIG:
+ _MISMATCH_LEVEL = logging.WARNING
+else:
+ _MISMATCH_LEVEL = logging.DEBUG
def _looks_like_bpo_44860() -> bool:
@@ -53,7 +78,7 @@ def _looks_like_bpo_44860() -> bool:
See <https://bugs.python.org/issue44860>.
"""
- from distutils.command.install import INSTALL_SCHEMES # type: ignore
+ from distutils.command.install import INSTALL_SCHEMES
try:
unix_user_platlib = INSTALL_SCHEMES["unix_user"]["platlib"]
@@ -64,8 +89,8 @@ def _looks_like_bpo_44860() -> bool:
def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool:
platlib = scheme["platlib"]
- if "/$platlibdir/" in platlib and hasattr(sys, "platlibdir"):
- platlib = platlib.replace("/$platlibdir/", f"/{sys.platlibdir}/")
+ if "/$platlibdir/" in platlib:
+ platlib = platlib.replace("/$platlibdir/", f"/{_PLATLIBDIR}/")
if "/lib64/" not in platlib:
return False
unpatched = platlib.replace("/lib64/", "/lib/")
@@ -78,7 +103,7 @@ def _looks_like_red_hat_lib() -> bool:
This is the only way I can see to tell a Red Hat-patched Python.
"""
- from distutils.command.install import INSTALL_SCHEMES # type: ignore
+ from distutils.command.install import INSTALL_SCHEMES
return all(
k in INSTALL_SCHEMES
@@ -90,7 +115,7 @@ def _looks_like_red_hat_lib() -> bool:
@functools.lru_cache(maxsize=None)
def _looks_like_debian_scheme() -> bool:
"""Debian adds two additional schemes."""
- from distutils.command.install import INSTALL_SCHEMES # type: ignore
+ from distutils.command.install import INSTALL_SCHEMES
return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES
@@ -116,6 +141,22 @@ def _looks_like_red_hat_scheme() -> bool:
@functools.lru_cache(maxsize=None)
+def _looks_like_slackware_scheme() -> bool:
+ """Slackware patches sysconfig but fails to patch distutils and site.
+
+ Slackware changes sysconfig's user scheme to use ``"lib64"`` for the lib
+ path, but does not do the same to the site module.
+ """
+ if user_site is None: # User-site not available.
+ return False
+ try:
+ paths = sysconfig.get_paths(scheme="posix_user", expand=False)
+ except KeyError: # User-site not available.
+ return False
+ return "/lib64/" in paths["purelib"] and "/lib64/" not in user_site
+
+
+@functools.lru_cache(maxsize=None)
def _looks_like_msys2_mingw_scheme() -> bool:
"""MSYS2 patches distutils and sysconfig to use a UNIX-like scheme.
@@ -133,9 +174,9 @@ def _looks_like_msys2_mingw_scheme() -> bool:
)
-def _fix_abiflags(parts: Tuple[str]) -> Iterator[str]:
+def _fix_abiflags(parts: Tuple[str]) -> Generator[str, None, None]:
ldversion = sysconfig.get_config_var("LDVERSION")
- abiflags: str = getattr(sys, "abiflags", None)
+ abiflags = getattr(sys, "abiflags", None)
# LDVERSION does not end with sys.abiflags. Just return the path unchanged.
if not ldversion or not abiflags or not ldversion.endswith(abiflags):
@@ -270,6 +311,17 @@ def get_scheme(
if skip_bpo_44860:
continue
+ # Slackware incorrectly patches posix_user to use lib64 instead of lib,
+ # but not usersite to match the location.
+ skip_slackware_user_scheme = (
+ user
+ and k in ("platlib", "purelib")
+ and not WINDOWS
+ and _looks_like_slackware_scheme()
+ )
+ if skip_slackware_user_scheme:
+ continue
+
# Both Debian and Red Hat patch Python to place the system site under
# /usr/local instead of /usr. Debian also places lib in dist-packages
# instead of site-packages, but the /usr/local check should cover it.
@@ -405,42 +457,11 @@ def get_platlib() -> str:
if _USE_SYSCONFIG:
return new
+ from . import _distutils
+
old = _distutils.get_platlib()
if _looks_like_deb_system_dist_packages(old):
return old
if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="platlib"):
_log_context()
return old
-
-
-def _deduplicated(v1: str, v2: str) -> List[str]:
- """Deduplicate values from a list."""
- if v1 == v2:
- return [v1]
- return [v1, v2]
-
-
-def get_prefixed_libs(prefix: str) -> List[str]:
- """Return the lib locations under ``prefix``."""
- new_pure, new_plat = _sysconfig.get_prefixed_libs(prefix)
- if _USE_SYSCONFIG:
- return _deduplicated(new_pure, new_plat)
-
- old_pure, old_plat = _distutils.get_prefixed_libs(prefix)
-
- warned = [
- _warn_if_mismatch(
- pathlib.Path(old_pure),
- pathlib.Path(new_pure),
- key="prefixed-purelib",
- ),
- _warn_if_mismatch(
- pathlib.Path(old_plat),
- pathlib.Path(new_plat),
- key="prefixed-platlib",
- ),
- ]
- if any(warned):
- _log_context(prefix=prefix)
-
- return _deduplicated(old_pure, old_plat)
diff --git a/src/pip/_internal/locations/_distutils.py b/src/pip/_internal/locations/_distutils.py
index 2ec79e65b..92bd93179 100644
--- a/src/pip/_internal/locations/_distutils.py
+++ b/src/pip/_internal/locations/_distutils.py
@@ -3,6 +3,17 @@
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
+# If pip's going to use distutils, it should not be using the copy that setuptools
+# might have injected into the environment. This is done by removing the injected
+# shim, if it's injected.
+#
+# See https://github.com/pypa/pip/issues/8761 for the original discussion and
+# rationale for why this is done within pip.
+try:
+ __import__("_distutils_hack").remove_shim()
+except (ImportError, AttributeError):
+ pass
+
import logging
import os
import sys
@@ -10,7 +21,7 @@ from distutils.cmd import Command as DistutilsCommand
from distutils.command.install import SCHEME_KEYS
from distutils.command.install import install as distutils_install_command
from distutils.sysconfig import get_python_lib
-from typing import Dict, List, Optional, Tuple, Union, cast
+from typing import Dict, List, Optional, Union, cast
from pip._internal.models.scheme import Scheme
from pip._internal.utils.compat import WINDOWS
@@ -24,10 +35,10 @@ logger = logging.getLogger(__name__)
def distutils_scheme(
dist_name: str,
user: bool = False,
- home: str = None,
- root: str = None,
+ home: Optional[str] = None,
+ root: Optional[str] = None,
isolated: bool = False,
- prefix: str = None,
+ prefix: Optional[str] = None,
*,
ignore_config_files: bool = False,
) -> Dict[str, str]:
@@ -84,7 +95,7 @@ def distutils_scheme(
if home:
prefix = home
elif user:
- prefix = i.install_userbase # type: ignore
+ prefix = i.install_userbase
else:
prefix = i.prefix
scheme["headers"] = os.path.join(
@@ -160,10 +171,3 @@ def get_purelib() -> str:
def get_platlib() -> str:
return get_python_lib(plat_specific=True)
-
-
-def get_prefixed_libs(prefix: str) -> Tuple[str, str]:
- return (
- get_python_lib(plat_specific=False, prefix=prefix),
- get_python_lib(plat_specific=True, prefix=prefix),
- )
diff --git a/src/pip/_internal/locations/_sysconfig.py b/src/pip/_internal/locations/_sysconfig.py
index 5e141aa1b..97aef1f1a 100644
--- a/src/pip/_internal/locations/_sysconfig.py
+++ b/src/pip/_internal/locations/_sysconfig.py
@@ -1,4 +1,3 @@
-import distutils.util # FIXME: For change_root.
import logging
import os
import sys
@@ -9,7 +8,7 @@ from pip._internal.exceptions import InvalidSchemeCombination, UserInstallationI
from pip._internal.models.scheme import SCHEME_KEYS, Scheme
from pip._internal.utils.virtualenv import running_under_virtualenv
-from .base import get_major_minor_version, is_osx_framework
+from .base import change_root, get_major_minor_version, is_osx_framework
logger = logging.getLogger(__name__)
@@ -194,7 +193,7 @@ def get_scheme(
)
if root is not None:
for key in SCHEME_KEYS:
- value = distutils.util.change_root(root, getattr(scheme, key))
+ value = change_root(root, getattr(scheme, key))
setattr(scheme, key, value)
return scheme
@@ -212,8 +211,3 @@ def get_purelib() -> str:
def get_platlib() -> str:
return sysconfig.get_paths()["platlib"]
-
-
-def get_prefixed_libs(prefix: str) -> typing.Tuple[str, str]:
- paths = sysconfig.get_paths(vars={"base": prefix, "platbase": prefix})
- return (paths["purelib"], paths["platlib"])
diff --git a/src/pip/_internal/locations/base.py b/src/pip/_internal/locations/base.py
index 86dad4a3a..3f9f896e6 100644
--- a/src/pip/_internal/locations/base.py
+++ b/src/pip/_internal/locations/base.py
@@ -5,6 +5,7 @@ import sys
import sysconfig
import typing
+from pip._internal.exceptions import InstallationError
from pip._internal.utils import appdirs
from pip._internal.utils.virtualenv import running_under_virtualenv
@@ -12,7 +13,7 @@ from pip._internal.utils.virtualenv import running_under_virtualenv
USER_CACHE_DIR = appdirs.user_cache_dir("pip")
# FIXME doesn't account for venv linked to global site-packages
-site_packages: typing.Optional[str] = sysconfig.get_path("purelib")
+site_packages: str = sysconfig.get_path("purelib")
def get_major_minor_version() -> str:
@@ -23,6 +24,34 @@ def get_major_minor_version() -> str:
return "{}.{}".format(*sys.version_info)
+def change_root(new_root: str, pathname: str) -> str:
+ """Return 'pathname' with 'new_root' prepended.
+
+ If 'pathname' is relative, this is equivalent to os.path.join(new_root, pathname).
+ Otherwise, it requires making 'pathname' relative and then joining the
+ two, which is tricky on DOS/Windows and Mac OS.
+
+ This is borrowed from Python's standard library's distutils module.
+ """
+ if os.name == "posix":
+ if not os.path.isabs(pathname):
+ return os.path.join(new_root, pathname)
+ else:
+ return os.path.join(new_root, pathname[1:])
+
+ elif os.name == "nt":
+ (drive, path) = os.path.splitdrive(pathname)
+ if path[0] == "\\":
+ path = path[1:]
+ return os.path.join(new_root, path)
+
+ else:
+ raise InstallationError(
+ f"Unknown platform: {os.name}\n"
+ "Can not change root path prefix on unknown platform."
+ )
+
+
def get_src_prefix() -> str:
if running_under_virtualenv():
src_prefix = os.path.join(sys.prefix, "src")
diff --git a/src/pip/_internal/metadata/__init__.py b/src/pip/_internal/metadata/__init__.py
index f4f2a4f6c..9f73ca710 100644
--- a/src/pip/_internal/metadata/__init__.py
+++ b/src/pip/_internal/metadata/__init__.py
@@ -1,7 +1,18 @@
-from typing import List, Optional
+import contextlib
+import functools
+import os
+import sys
+from typing import TYPE_CHECKING, List, Optional, Type, cast
+
+from pip._internal.utils.misc import strtobool
from .base import BaseDistribution, BaseEnvironment, FilesystemWheel, MemoryWheel, Wheel
+if TYPE_CHECKING:
+ from typing import Protocol
+else:
+ Protocol = object
+
__all__ = [
"BaseDistribution",
"BaseEnvironment",
@@ -11,9 +22,49 @@ __all__ = [
"get_default_environment",
"get_environment",
"get_wheel_distribution",
+ "select_backend",
]
+def _should_use_importlib_metadata() -> bool:
+ """Whether to use the ``importlib.metadata`` or ``pkg_resources`` backend.
+
+ By default, pip uses ``importlib.metadata`` on Python 3.11+, and
+ ``pkg_resourcess`` otherwise. This can be overridden by a couple of ways:
+
+ * If environment variable ``_PIP_USE_IMPORTLIB_METADATA`` is set, it
+ dictates whether ``importlib.metadata`` is used, regardless of Python
+ version.
+ * On Python 3.11+, Python distributors can patch ``importlib.metadata``
+ to add a global constant ``_PIP_USE_IMPORTLIB_METADATA = False``. This
+ makes pip use ``pkg_resources`` (unless the user set the aforementioned
+ environment variable to *True*).
+ """
+ with contextlib.suppress(KeyError, ValueError):
+ return bool(strtobool(os.environ["_PIP_USE_IMPORTLIB_METADATA"]))
+ if sys.version_info < (3, 11):
+ return False
+ import importlib.metadata
+
+ return bool(getattr(importlib.metadata, "_PIP_USE_IMPORTLIB_METADATA", True))
+
+
+class Backend(Protocol):
+ Distribution: Type[BaseDistribution]
+ Environment: Type[BaseEnvironment]
+
+
+@functools.lru_cache(maxsize=None)
+def select_backend() -> Backend:
+ if _should_use_importlib_metadata():
+ from . import importlib
+
+ return cast(Backend, importlib)
+ from . import pkg_resources
+
+ return cast(Backend, pkg_resources)
+
+
def get_default_environment() -> BaseEnvironment:
"""Get the default representation for the current environment.
@@ -21,9 +72,7 @@ def get_default_environment() -> BaseEnvironment:
Environment instance should be built from ``sys.path`` and may use caching
to share instance state accorss calls.
"""
- from .pkg_resources import Environment
-
- return Environment.default()
+ return select_backend().Environment.default()
def get_environment(paths: Optional[List[str]]) -> BaseEnvironment:
@@ -33,9 +82,16 @@ def get_environment(paths: Optional[List[str]]) -> BaseEnvironment:
given import paths. The backend must build a fresh instance representing
the state of installed distributions when this function is called.
"""
- from .pkg_resources import Environment
+ return select_backend().Environment.from_paths(paths)
- return Environment.from_paths(paths)
+
+def get_directory_distribution(directory: str) -> BaseDistribution:
+ """Get the distribution metadata representation in the specified directory.
+
+ This returns a Distribution instance from the chosen backend based on
+ the given on-disk ``.dist-info`` directory.
+ """
+ return select_backend().Distribution.from_directory(directory)
def get_wheel_distribution(wheel: Wheel, canonical_name: str) -> BaseDistribution:
@@ -46,6 +102,26 @@ def get_wheel_distribution(wheel: Wheel, canonical_name: str) -> BaseDistributio
:param canonical_name: Normalized project name of the given wheel.
"""
- from .pkg_resources import Distribution
+ return select_backend().Distribution.from_wheel(wheel, canonical_name)
+
+
+def get_metadata_distribution(
+ metadata_contents: bytes,
+ filename: str,
+ canonical_name: str,
+) -> BaseDistribution:
+ """Get the dist representation of the specified METADATA file contents.
- return Distribution.from_wheel(wheel, canonical_name)
+ This returns a Distribution instance from the chosen backend sourced from the data
+ in `metadata_contents`.
+
+ :param metadata_contents: Contents of a METADATA file within a dist, or one served
+ via PEP 658.
+ :param filename: Filename for the dist this metadata represents.
+ :param canonical_name: Normalized project name of the given dist.
+ """
+ return select_backend().Distribution.from_metadata_file_contents(
+ metadata_contents,
+ filename,
+ canonical_name,
+ )
diff --git a/src/pip/_internal/metadata/_json.py b/src/pip/_internal/metadata/_json.py
new file mode 100644
index 000000000..336b52f1e
--- /dev/null
+++ b/src/pip/_internal/metadata/_json.py
@@ -0,0 +1,84 @@
+# Extracted from https://github.com/pfmoore/pkg_metadata
+
+from email.header import Header, decode_header, make_header
+from email.message import Message
+from typing import Any, Dict, List, Union
+
+METADATA_FIELDS = [
+ # Name, Multiple-Use
+ ("Metadata-Version", False),
+ ("Name", False),
+ ("Version", False),
+ ("Dynamic", True),
+ ("Platform", True),
+ ("Supported-Platform", True),
+ ("Summary", False),
+ ("Description", False),
+ ("Description-Content-Type", False),
+ ("Keywords", False),
+ ("Home-page", False),
+ ("Download-URL", False),
+ ("Author", False),
+ ("Author-email", False),
+ ("Maintainer", False),
+ ("Maintainer-email", False),
+ ("License", False),
+ ("Classifier", True),
+ ("Requires-Dist", True),
+ ("Requires-Python", False),
+ ("Requires-External", True),
+ ("Project-URL", True),
+ ("Provides-Extra", True),
+ ("Provides-Dist", True),
+ ("Obsoletes-Dist", True),
+]
+
+
+def json_name(field: str) -> str:
+ return field.lower().replace("-", "_")
+
+
+def msg_to_json(msg: Message) -> Dict[str, Any]:
+ """Convert a Message object into a JSON-compatible dictionary."""
+
+ def sanitise_header(h: Union[Header, str]) -> str:
+ if isinstance(h, Header):
+ chunks = []
+ for bytes, encoding in decode_header(h):
+ if encoding == "unknown-8bit":
+ try:
+ # See if UTF-8 works
+ bytes.decode("utf-8")
+ encoding = "utf-8"
+ except UnicodeDecodeError:
+ # If not, latin1 at least won't fail
+ encoding = "latin1"
+ chunks.append((bytes, encoding))
+ return str(make_header(chunks))
+ return str(h)
+
+ result = {}
+ for field, multi in METADATA_FIELDS:
+ if field not in msg:
+ continue
+ key = json_name(field)
+ if multi:
+ value: Union[str, List[str]] = [
+ sanitise_header(v) for v in msg.get_all(field)
+ ]
+ else:
+ value = sanitise_header(msg.get(field))
+ if key == "keywords":
+ # Accept both comma-separated and space-separated
+ # forms, for better compatibility with old data.
+ if "," in value:
+ value = [v.strip() for v in value.split(",")]
+ else:
+ value = value.split()
+ result[key] = value
+
+ payload = msg.get_payload()
+ if payload:
+ result["description"] = payload
+
+ return result
diff --git a/src/pip/_internal/metadata/base.py b/src/pip/_internal/metadata/base.py
index 4788360a0..cafb79fb3 100644
--- a/src/pip/_internal/metadata/base.py
+++ b/src/pip/_internal/metadata/base.py
@@ -1,17 +1,24 @@
+import csv
import email.message
+import functools
import json
import logging
+import pathlib
import re
import zipfile
from typing import (
IO,
TYPE_CHECKING,
+ Any,
Collection,
Container,
+ Dict,
Iterable,
Iterator,
List,
+ NamedTuple,
Optional,
+ Tuple,
Union,
)
@@ -20,6 +27,8 @@ from pip._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet
from pip._vendor.packaging.utils import NormalizedName
from pip._vendor.packaging.version import LegacyVersion, Version
+from pip._internal.exceptions import NoneMetadataError
+from pip._internal.locations import site_packages, user_site
from pip._internal.models.direct_url import (
DIRECT_URL_METADATA_NAME,
DirectUrl,
@@ -27,8 +36,12 @@ from pip._internal.models.direct_url import (
)
from pip._internal.utils.compat import stdlib_pkgs # TODO: Move definition here.
from pip._internal.utils.egg_link import egg_link_path_from_sys_path
+from pip._internal.utils.misc import is_local, normalize_path
+from pip._internal.utils.packaging import safe_extra
from pip._internal.utils.urls import url_to_path
+from ._json import msg_to_json
+
if TYPE_CHECKING:
from typing import Protocol
else:
@@ -36,6 +49,8 @@ else:
DistributionVersion = Union[LegacyVersion, Version]
+InfoPath = Union[str, pathlib.PurePath]
+
logger = logging.getLogger(__name__)
@@ -53,7 +68,83 @@ class BaseEntryPoint(Protocol):
raise NotImplementedError()
+def _convert_installed_files_path(
+ entry: Tuple[str, ...],
+ info: Tuple[str, ...],
+) -> str:
+ """Convert a legacy installed-files.txt path into modern RECORD path.
+
+ The legacy format stores paths relative to the info directory, while the
+ modern format stores paths relative to the package root, e.g. the
+ site-packages directory.
+
+ :param entry: Path parts of the installed-files.txt entry.
+ :param info: Path parts of the egg-info directory relative to package root.
+ :returns: The converted entry.
+
+ For best compatibility with symlinks, this does not use ``abspath()`` or
+ ``Path.resolve()``, but tries to work with path parts:
+
+ 1. While ``entry`` starts with ``..``, remove the equal amounts of parts
+ from ``info``; if ``info`` is empty, start appending ``..`` instead.
+ 2. Join the two directly.
+ """
+ while entry and entry[0] == "..":
+ if not info or info[-1] == "..":
+ info += ("..",)
+ else:
+ info = info[:-1]
+ entry = entry[1:]
+ return str(pathlib.Path(*info, *entry))
+
+
+class RequiresEntry(NamedTuple):
+ requirement: str
+ extra: str
+ marker: str
+
+
class BaseDistribution(Protocol):
+ @classmethod
+ def from_directory(cls, directory: str) -> "BaseDistribution":
+ """Load the distribution from a metadata directory.
+
+ :param directory: Path to a metadata directory, e.g. ``.dist-info``.
+ """
+ raise NotImplementedError()
+
+ @classmethod
+ def from_metadata_file_contents(
+ cls,
+ metadata_contents: bytes,
+ filename: str,
+ project_name: str,
+ ) -> "BaseDistribution":
+ """Load the distribution from the contents of a METADATA file.
+
+ This is used to implement PEP 658 by generating a "shallow" dist object that can
+ be used for resolution without downloading or building the actual dist yet.
+
+ :param metadata_contents: The contents of a METADATA file.
+ :param filename: File name for the dist with this metadata.
+ :param project_name: Name of the project this dist represents.
+ """
+ raise NotImplementedError()
+
+ @classmethod
+ def from_wheel(cls, wheel: "Wheel", name: str) -> "BaseDistribution":
+ """Load the distribution from a given wheel.
+
+ :param wheel: A concrete wheel definition.
+ :param name: File name of the wheel.
+
+ :raises InvalidWheel: Whenever loading of the wheel causes a
+ :py:exc:`zipfile.BadZipFile` exception to be thrown.
+ :raises UnsupportedWheel: If the wheel is a valid zip, but malformed
+ internally.
+ """
+ raise NotImplementedError()
+
def __repr__(self) -> str:
return f"{self.raw_name} {self.version} ({self.location})"
@@ -97,8 +188,21 @@ class BaseDistribution(Protocol):
return None
@property
- def info_directory(self) -> Optional[str]:
- """Location of the .[egg|dist]-info directory.
+ def installed_location(self) -> Optional[str]:
+ """The distribution's "installed" location.
+
+ This should generally be a ``site-packages`` directory. This is
+ usually ``dist.location``, except for legacy develop-installed packages,
+ where ``dist.location`` is the source code location, and this is where
+ the ``.egg-link`` file is.
+
+ The returned location is normalized (in particular, with symlinks removed).
+ """
+ raise NotImplementedError()
+
+ @property
+ def info_location(self) -> Optional[str]:
+ """Location of the .[egg|dist]-info directory or file.
Similarly to ``location``, a string value is not necessarily a
filesystem path. ``None`` means the distribution is created in-memory.
@@ -113,6 +217,65 @@ class BaseDistribution(Protocol):
raise NotImplementedError()
@property
+ def installed_by_distutils(self) -> bool:
+ """Whether this distribution is installed with legacy distutils format.
+
+ A distribution installed with "raw" distutils not patched by setuptools
+ uses one single file at ``info_location`` to store metadata. We need to
+ treat this specially on uninstallation.
+ """
+ info_location = self.info_location
+ if not info_location:
+ return False
+ return pathlib.Path(info_location).is_file()
+
+ @property
+ def installed_as_egg(self) -> bool:
+ """Whether this distribution is installed as an egg.
+
+ This usually indicates the distribution was installed by (older versions
+ of) easy_install.
+ """
+ location = self.location
+ if not location:
+ return False
+ return location.endswith(".egg")
+
+ @property
+ def installed_with_setuptools_egg_info(self) -> bool:
+ """Whether this distribution is installed with the ``.egg-info`` format.
+
+ This usually indicates the distribution was installed with setuptools
+ with an old pip version or with ``single-version-externally-managed``.
+
+ Note that this ensure the metadata store is a directory. distutils can
+ also installs an ``.egg-info``, but as a file, not a directory. This
+ property is *False* for that case. Also see ``installed_by_distutils``.
+ """
+ info_location = self.info_location
+ if not info_location:
+ return False
+ if not info_location.endswith(".egg-info"):
+ return False
+ return pathlib.Path(info_location).is_dir()
+
+ @property
+ def installed_with_dist_info(self) -> bool:
+ """Whether this distribution is installed with the "modern format".
+
+ This indicates a "modern" installation, e.g. storing metadata in the
+ ``.dist-info`` directory. This applies to installations made by
+ setuptools (but through pip, not directly), or anything using the
+ standardized build backend interface (PEP 517).
+ """
+ info_location = self.info_location
+ if not info_location:
+ return False
+ if not info_location.endswith(".dist-info"):
+ return False
+ return pathlib.Path(info_location).is_dir()
+
+ @property
def canonical_name(self) -> NormalizedName:
raise NotImplementedError()
@@ -121,6 +284,14 @@ class BaseDistribution(Protocol):
raise NotImplementedError()
@property
+ def setuptools_filename(self) -> str:
+ """Convert a project name to its setuptools-compatible filename.
+
+ This is a copy of ``pkg_resources.to_filename()`` for compatibility.
+ """
+ return self.raw_name.replace("-", "_")
+
+ @property
def direct_url(self) -> Optional[DirectUrl]:
"""Obtain a DirectUrl from this distribution.
@@ -148,7 +319,19 @@ class BaseDistribution(Protocol):
@property
def installer(self) -> str:
- raise NotImplementedError()
+ try:
+ installer_text = self.read_text("INSTALLER")
+ except (OSError, ValueError, NoneMetadataError):
+ return "" # Fail silently if the installer file cannot be read.
+ for line in installer_text.splitlines():
+ cleaned_line = line.strip()
+ if cleaned_line:
+ return cleaned_line
+ return ""
+
+ @property
+ def requested(self) -> bool:
+ return self.is_file("REQUESTED")
@property
def editable(self) -> bool:
@@ -156,31 +339,82 @@ class BaseDistribution(Protocol):
@property
def local(self) -> bool:
- raise NotImplementedError()
+ """If distribution is installed in the current virtual environment.
+
+ Always True if we're not in a virtualenv.
+ """
+ if self.installed_location is None:
+ return False
+ return is_local(self.installed_location)
@property
def in_usersite(self) -> bool:
- raise NotImplementedError()
+ if self.installed_location is None or user_site is None:
+ return False
+ return self.installed_location.startswith(normalize_path(user_site))
@property
def in_site_packages(self) -> bool:
+ if self.installed_location is None or site_packages is None:
+ return False
+ return self.installed_location.startswith(normalize_path(site_packages))
+
+ def is_file(self, path: InfoPath) -> bool:
+ """Check whether an entry in the info directory is a file."""
+ raise NotImplementedError()
+
+ def iter_distutils_script_names(self) -> Iterator[str]:
+ """Find distutils 'scripts' entries metadata.
+
+ If 'scripts' is supplied in ``setup.py``, distutils records those in the
+ installed distribution's ``scripts`` directory, a file for each script.
+ """
raise NotImplementedError()
- def read_text(self, name: str) -> str:
- """Read a file in the .dist-info (or .egg-info) directory.
+ def read_text(self, path: InfoPath) -> str:
+ """Read a file in the info directory.
- Should raise ``FileNotFoundError`` if ``name`` does not exist in the
- metadata directory.
+ :raise FileNotFoundError: If ``path`` does not exist in the directory.
+ :raise NoneMetadataError: If ``path`` exists in the info directory, but
+ cannot be read.
"""
raise NotImplementedError()
def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
raise NotImplementedError()
+ def _metadata_impl(self) -> email.message.Message:
+ raise NotImplementedError()
+
+ @functools.lru_cache(maxsize=1)
+ def _metadata_cached(self) -> email.message.Message:
+ # When we drop python 3.7 support, move this to the metadata property and use
+ # functools.cached_property instead of lru_cache.
+ metadata = self._metadata_impl()
+ self._add_egg_info_requires(metadata)
+ return metadata
+
@property
def metadata(self) -> email.message.Message:
- """Metadata of distribution parsed from e.g. METADATA or PKG-INFO."""
- raise NotImplementedError()
+ """Metadata of distribution parsed from e.g. METADATA or PKG-INFO.
+
+ This should return an empty message if the metadata file is unavailable.
+
+ :raises NoneMetadataError: If the metadata file is available, but does
+ not contain valid metadata.
+ """
+ return self._metadata_cached()
+
+ @property
+ def metadata_dict(self) -> Dict[str, Any]:
+ """PEP 566 compliant JSON-serializable representation of METADATA or PKG-INFO.
+
+ This should return an empty dict if the metadata file is unavailable.
+
+ :raises NoneMetadataError: If the metadata file is available, but does
+ not contain valid metadata.
+ """
+ return msg_to_json(self.metadata)
@property
def metadata_version(self) -> Optional[str]:
@@ -229,6 +463,121 @@ class BaseDistribution(Protocol):
"""
raise NotImplementedError()
+ def _iter_declared_entries_from_record(self) -> Optional[Iterator[str]]:
+ try:
+ text = self.read_text("RECORD")
+ except FileNotFoundError:
+ return None
+ # This extra Path-str cast normalizes entries.
+ return (str(pathlib.Path(row[0])) for row in csv.reader(text.splitlines()))
+
+ def _iter_declared_entries_from_legacy(self) -> Optional[Iterator[str]]:
+ try:
+ text = self.read_text("installed-files.txt")
+ except FileNotFoundError:
+ return None
+ paths = (p for p in text.splitlines(keepends=False) if p)
+ root = self.location
+ info = self.info_location
+ if root is None or info is None:
+ return paths
+ try:
+ info_rel = pathlib.Path(info).relative_to(root)
+ except ValueError: # info is not relative to root.
+ return paths
+ if not info_rel.parts: # info *is* root.
+ return paths
+ return (
+ _convert_installed_files_path(pathlib.Path(p).parts, info_rel.parts)
+ for p in paths
+ )
+
+ def iter_declared_entries(self) -> Optional[Iterator[str]]:
+ """Iterate through file entries declared in this distribution.
+
+ For modern .dist-info distributions, this is the files listed in the
+ ``RECORD`` metadata file. For legacy setuptools distributions, this
+ comes from ``installed-files.txt``, with entries normalized to be
+ compatible with the format used by ``RECORD``.
+
+ :return: An iterator for listed entries, or None if the distribution
+ contains neither ``RECORD`` nor ``installed-files.txt``.
+ """
+ return (
+ self._iter_declared_entries_from_record()
+ or self._iter_declared_entries_from_legacy()
+ )
+
+ def _iter_requires_txt_entries(self) -> Iterator[RequiresEntry]:
+ """Parse a ``requires.txt`` in an egg-info directory.
+
+ This is an INI-ish format where an egg-info stores dependencies. A
+ section name describes extra other environment markers, while each entry
+ is an arbitrary string (not a key-value pair) representing a dependency
+ as a requirement string (no markers).
+
+ There is a construct in ``importlib.metadata`` called ``Sectioned`` that
+ does mostly the same, but the format is currently considered private.
+ """
+ try:
+ content = self.read_text("requires.txt")
+ except FileNotFoundError:
+ return
+ extra = marker = "" # Section-less entries don't have markers.
+ for line in content.splitlines():
+ line = line.strip()
+ if not line or line.startswith("#"): # Comment; ignored.
+ continue
+ if line.startswith("[") and line.endswith("]"): # A section header.
+ extra, _, marker = line.strip("[]").partition(":")
+ continue
+ yield RequiresEntry(requirement=line, extra=extra, marker=marker)
+
+ def _iter_egg_info_extras(self) -> Iterable[str]:
+ """Get extras from the egg-info directory."""
+ known_extras = {""}
+ for entry in self._iter_requires_txt_entries():
+ if entry.extra in known_extras:
+ continue
+ known_extras.add(entry.extra)
+ yield entry.extra
+
+ def _iter_egg_info_dependencies(self) -> Iterable[str]:
+ """Get distribution dependencies from the egg-info directory.
+
+ To ease parsing, this converts a legacy dependency entry into a PEP 508
+ requirement string. Like ``_iter_requires_txt_entries()``, there is code
+ in ``importlib.metadata`` that does mostly the same, but not do exactly
+ what we need.
+
+ Namely, ``importlib.metadata`` does not normalize the extra name before
+ putting it into the requirement string, which causes marker comparison
+ to fail because the dist-info format do normalize. This is consistent in
+ all currently available PEP 517 backends, although not standardized.
+ """
+ for entry in self._iter_requires_txt_entries():
+ if entry.extra and entry.marker:
+ marker = f'({entry.marker}) and extra == "{safe_extra(entry.extra)}"'
+ elif entry.extra:
+ marker = f'extra == "{safe_extra(entry.extra)}"'
+ elif entry.marker:
+ marker = entry.marker
+ else:
+ marker = ""
+ if marker:
+ yield f"{entry.requirement} ; {marker}"
+ else:
+ yield entry.requirement
+
+ def _add_egg_info_requires(self, metadata: email.message.Message) -> None:
+ """Add egg-info requires.txt information to the metadata."""
+ if not metadata.get_all("Requires-Dist"):
+ for dep in self._iter_egg_info_dependencies():
+ metadata["Requires-Dist"] = dep
+ if not metadata.get_all("Provides-Extra"):
+ for extra in self._iter_egg_info_extras():
+ metadata["Provides-Extra"] = extra
+
class BaseEnvironment:
"""An environment containing distributions to introspect."""
@@ -242,7 +591,11 @@ class BaseEnvironment:
raise NotImplementedError()
def get_distribution(self, name: str) -> Optional["BaseDistribution"]:
- """Given a requirement name, return the installed distributions."""
+ """Given a requirement name, return the installed distributions.
+
+ The name may not be normalized. The implementation must canonicalize
+ it for lookup.
+ """
raise NotImplementedError()
def _iter_distributions(self) -> Iterator["BaseDistribution"]:
@@ -254,8 +607,8 @@ class BaseEnvironment:
"""
raise NotImplementedError()
- def iter_distributions(self) -> Iterator["BaseDistribution"]:
- """Iterate through installed distributions."""
+ def iter_all_distributions(self) -> Iterator[BaseDistribution]:
+ """Iterate through all installed distributions without any filtering."""
for dist in self._iter_distributions():
# Make sure the distribution actually comes from a valid Python
# packaging distribution. Pip's AdjacentTempDirectory leaves folders
@@ -285,6 +638,11 @@ class BaseEnvironment:
) -> Iterator[BaseDistribution]:
"""Return a list of installed distributions.
+ This is based on ``iter_all_distributions()`` with additional filtering
+ options. Note that ``iter_installed_distributions()`` without arguments
+ is *not* equal to ``iter_all_distributions()``, since some of the
+ configurations exclude packages by default.
+
:param local_only: If True (default), only return installations
local to the current virtualenv, if in a virtualenv.
:param skip: An iterable of canonicalized project names to ignore;
@@ -294,7 +652,7 @@ class BaseEnvironment:
:param user_only: If True, only report installations in the user
site directory.
"""
- it = self.iter_distributions()
+ it = self.iter_all_distributions()
if local_only:
it = (d for d in it if d.local)
if not include_editables:
diff --git a/src/pip/_internal/metadata/importlib/__init__.py b/src/pip/_internal/metadata/importlib/__init__.py
new file mode 100644
index 000000000..5e7af9fe5
--- /dev/null
+++ b/src/pip/_internal/metadata/importlib/__init__.py
@@ -0,0 +1,4 @@
+from ._dists import Distribution
+from ._envs import Environment
+
+__all__ = ["Distribution", "Environment"]
diff --git a/src/pip/_internal/metadata/importlib/_compat.py b/src/pip/_internal/metadata/importlib/_compat.py
new file mode 100644
index 000000000..593bff23e
--- /dev/null
+++ b/src/pip/_internal/metadata/importlib/_compat.py
@@ -0,0 +1,55 @@
+import importlib.metadata
+from typing import Any, Optional, Protocol, cast
+
+
+class BadMetadata(ValueError):
+ def __init__(self, dist: importlib.metadata.Distribution, *, reason: str) -> None:
+ self.dist = dist
+ self.reason = reason
+
+ def __str__(self) -> str:
+ return f"Bad metadata in {self.dist} ({self.reason})"
+
+
+class BasePath(Protocol):
+ """A protocol that various path objects conform.
+
+ This exists because importlib.metadata uses both ``pathlib.Path`` and
+ ``zipfile.Path``, and we need a common base for type hints (Union does not
+ work well since ``zipfile.Path`` is too new for our linter setup).
+
+ This does not mean to be exhaustive, but only contains things that present
+ in both classes *that we need*.
+ """
+
+ @property
+ def name(self) -> str:
+ raise NotImplementedError()
+
+ @property
+ def parent(self) -> "BasePath":
+ raise NotImplementedError()
+
+
+def get_info_location(d: importlib.metadata.Distribution) -> Optional[BasePath]:
+ """Find the path to the distribution's metadata directory.
+
+ HACK: This relies on importlib.metadata's private ``_path`` attribute. Not
+ all distributions exist on disk, so importlib.metadata is correct to not
+ expose the attribute as public. But pip's code base is old and not as clean,
+ so we do this to avoid having to rewrite too many things. Hopefully we can
+ eliminate this some day.
+ """
+ return getattr(d, "_path", None)
+
+
+def get_dist_name(dist: importlib.metadata.Distribution) -> str:
+ """Get the distribution's project name.
+
+ The ``name`` attribute is only available in Python 3.10 or later. We are
+ targeting exactly that, but Mypy does not know this.
+ """
+ name = cast(Any, dist).name
+ if not isinstance(name, str):
+ raise BadMetadata(dist, reason="invalid metadata entry 'name'")
+ return name
diff --git a/src/pip/_internal/metadata/importlib/_dists.py b/src/pip/_internal/metadata/importlib/_dists.py
new file mode 100644
index 000000000..65c043c87
--- /dev/null
+++ b/src/pip/_internal/metadata/importlib/_dists.py
@@ -0,0 +1,224 @@
+import email.message
+import importlib.metadata
+import os
+import pathlib
+import zipfile
+from typing import (
+ Collection,
+ Dict,
+ Iterable,
+ Iterator,
+ Mapping,
+ Optional,
+ Sequence,
+ cast,
+)
+
+from pip._vendor.packaging.requirements import Requirement
+from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
+from pip._vendor.packaging.version import parse as parse_version
+
+from pip._internal.exceptions import InvalidWheel, UnsupportedWheel
+from pip._internal.metadata.base import (
+ BaseDistribution,
+ BaseEntryPoint,
+ DistributionVersion,
+ InfoPath,
+ Wheel,
+)
+from pip._internal.utils.misc import normalize_path
+from pip._internal.utils.packaging import safe_extra
+from pip._internal.utils.temp_dir import TempDirectory
+from pip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file
+
+from ._compat import BasePath, get_dist_name
+
+
+class WheelDistribution(importlib.metadata.Distribution):
+ """An ``importlib.metadata.Distribution`` read from a wheel.
+
+ Although ``importlib.metadata.PathDistribution`` accepts ``zipfile.Path``,
+ its implementation is too "lazy" for pip's needs (we can't keep the ZipFile
+ handle open for the entire lifetime of the distribution object).
+
+ This implementation eagerly reads the entire metadata directory into the
+ memory instead, and operates from that.
+ """
+
+ def __init__(
+ self,
+ files: Mapping[pathlib.PurePosixPath, bytes],
+ info_location: pathlib.PurePosixPath,
+ ) -> None:
+ self._files = files
+ self.info_location = info_location
+
+ @classmethod
+ def from_zipfile(
+ cls,
+ zf: zipfile.ZipFile,
+ name: str,
+ location: str,
+ ) -> "WheelDistribution":
+ info_dir, _ = parse_wheel(zf, name)
+ paths = (
+ (name, pathlib.PurePosixPath(name.split("/", 1)[-1]))
+ for name in zf.namelist()
+ if name.startswith(f"{info_dir}/")
+ )
+ files = {
+ relpath: read_wheel_metadata_file(zf, fullpath)
+ for fullpath, relpath in paths
+ }
+ info_location = pathlib.PurePosixPath(location, info_dir)
+ return cls(files, info_location)
+
+ def iterdir(self, path: InfoPath) -> Iterator[pathlib.PurePosixPath]:
+ # Only allow iterating through the metadata directory.
+ if pathlib.PurePosixPath(str(path)) in self._files:
+ return iter(self._files)
+ raise FileNotFoundError(path)
+
+ def read_text(self, filename: str) -> Optional[str]:
+ try:
+ data = self._files[pathlib.PurePosixPath(filename)]
+ except KeyError:
+ return None
+ try:
+ text = data.decode("utf-8")
+ except UnicodeDecodeError as e:
+ wheel = self.info_location.parent
+ error = f"Error decoding metadata for {wheel}: {e} in {filename} file"
+ raise UnsupportedWheel(error)
+ return text
+
+
+class Distribution(BaseDistribution):
+ def __init__(
+ self,
+ dist: importlib.metadata.Distribution,
+ info_location: Optional[BasePath],
+ installed_location: Optional[BasePath],
+ ) -> None:
+ self._dist = dist
+ self._info_location = info_location
+ self._installed_location = installed_location
+
+ @classmethod
+ def from_directory(cls, directory: str) -> BaseDistribution:
+ info_location = pathlib.Path(directory)
+ dist = importlib.metadata.Distribution.at(info_location)
+ return cls(dist, info_location, info_location.parent)
+
+ @classmethod
+ def from_metadata_file_contents(
+ cls,
+ metadata_contents: bytes,
+ filename: str,
+ project_name: str,
+ ) -> BaseDistribution:
+ # Generate temp dir to contain the metadata file, and write the file contents.
+ temp_dir = pathlib.Path(
+ TempDirectory(kind="metadata", globally_managed=True).path
+ )
+ metadata_path = temp_dir / "METADATA"
+ metadata_path.write_bytes(metadata_contents)
+ # Construct dist pointing to the newly created directory.
+ dist = importlib.metadata.Distribution.at(metadata_path.parent)
+ return cls(dist, metadata_path.parent, None)
+
+ @classmethod
+ def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
+ try:
+ with wheel.as_zipfile() as zf:
+ dist = WheelDistribution.from_zipfile(zf, name, wheel.location)
+ except zipfile.BadZipFile as e:
+ raise InvalidWheel(wheel.location, name) from e
+ except UnsupportedWheel as e:
+ raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
+ return cls(dist, dist.info_location, pathlib.PurePosixPath(wheel.location))
+
+ @property
+ def location(self) -> Optional[str]:
+ if self._info_location is None:
+ return None
+ return str(self._info_location.parent)
+
+ @property
+ def info_location(self) -> Optional[str]:
+ if self._info_location is None:
+ return None
+ return str(self._info_location)
+
+ @property
+ def installed_location(self) -> Optional[str]:
+ if self._installed_location is None:
+ return None
+ return normalize_path(str(self._installed_location))
+
+ def _get_dist_name_from_location(self) -> Optional[str]:
+ """Try to get the name from the metadata directory name.
+
+ This is much faster than reading metadata.
+ """
+ if self._info_location is None:
+ return None
+ stem, suffix = os.path.splitext(self._info_location.name)
+ if suffix not in (".dist-info", ".egg-info"):
+ return None
+ return stem.split("-", 1)[0]
+
+ @property
+ def canonical_name(self) -> NormalizedName:
+ name = self._get_dist_name_from_location() or get_dist_name(self._dist)
+ return canonicalize_name(name)
+
+ @property
+ def version(self) -> DistributionVersion:
+ return parse_version(self._dist.version)
+
+ def is_file(self, path: InfoPath) -> bool:
+ return self._dist.read_text(str(path)) is not None
+
+ def iter_distutils_script_names(self) -> Iterator[str]:
+ # A distutils installation is always "flat" (not in e.g. egg form), so
+ # if this distribution's info location is NOT a pathlib.Path (but e.g.
+ # zipfile.Path), it can never contain any distutils scripts.
+ if not isinstance(self._info_location, pathlib.Path):
+ return
+ for child in self._info_location.joinpath("scripts").iterdir():
+ yield child.name
+
+ def read_text(self, path: InfoPath) -> str:
+ content = self._dist.read_text(str(path))
+ if content is None:
+ raise FileNotFoundError(path)
+ return content
+
+ def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
+ # importlib.metadata's EntryPoint structure sasitfies BaseEntryPoint.
+ return self._dist.entry_points
+
+ def _metadata_impl(self) -> email.message.Message:
+ # From Python 3.10+, importlib.metadata declares PackageMetadata as the
+ # return type. This protocol is unfortunately a disaster now and misses
+ # a ton of fields that we need, including get() and get_payload(). We
+ # rely on the implementation that the object is actually a Message now,
+ # until upstream can improve the protocol. (python/cpython#94952)
+ return cast(email.message.Message, self._dist.metadata)
+
+ def iter_provided_extras(self) -> Iterable[str]:
+ return (
+ safe_extra(extra) for extra in self.metadata.get_all("Provides-Extra", [])
+ )
+
+ def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
+ contexts: Sequence[Dict[str, str]] = [{"extra": safe_extra(e)} for e in extras]
+ for req_string in self.metadata.get_all("Requires-Dist", []):
+ req = Requirement(req_string)
+ if not req.marker:
+ yield req
+ elif not extras and req.marker.evaluate({"extra": ""}):
+ yield req
+ elif any(req.marker.evaluate(context) for context in contexts):
+ yield req
diff --git a/src/pip/_internal/metadata/importlib/_envs.py b/src/pip/_internal/metadata/importlib/_envs.py
new file mode 100644
index 000000000..cbec59e2c
--- /dev/null
+++ b/src/pip/_internal/metadata/importlib/_envs.py
@@ -0,0 +1,188 @@
+import functools
+import importlib.metadata
+import logging
+import os
+import pathlib
+import sys
+import zipfile
+import zipimport
+from typing import Iterator, List, Optional, Sequence, Set, Tuple
+
+from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
+
+from pip._internal.metadata.base import BaseDistribution, BaseEnvironment
+from pip._internal.models.wheel import Wheel
+from pip._internal.utils.deprecation import deprecated
+from pip._internal.utils.filetypes import WHEEL_EXTENSION
+
+from ._compat import BadMetadata, BasePath, get_dist_name, get_info_location
+from ._dists import Distribution
+
+logger = logging.getLogger(__name__)
+
+
+def _looks_like_wheel(location: str) -> bool:
+ if not location.endswith(WHEEL_EXTENSION):
+ return False
+ if not os.path.isfile(location):
+ return False
+ if not Wheel.wheel_file_re.match(os.path.basename(location)):
+ return False
+ return zipfile.is_zipfile(location)
+
+
+class _DistributionFinder:
+ """Finder to locate distributions.
+
+ The main purpose of this class is to memoize found distributions' names, so
+ only one distribution is returned for each package name. At lot of pip code
+ assumes this (because it is setuptools's behavior), and not doing the same
+ can potentially cause a distribution in lower precedence path to override a
+ higher precedence one if the caller is not careful.
+
+ Eventually we probably want to make it possible to see lower precedence
+ installations as well. It's useful feature, after all.
+ """
+
+ FoundResult = Tuple[importlib.metadata.Distribution, Optional[BasePath]]
+
+ def __init__(self) -> None:
+ self._found_names: Set[NormalizedName] = set()
+
+ def _find_impl(self, location: str) -> Iterator[FoundResult]:
+ """Find distributions in a location."""
+ # Skip looking inside a wheel. Since a package inside a wheel is not
+ # always valid (due to .data directories etc.), its .dist-info entry
+ # should not be considered an installed distribution.
+ if _looks_like_wheel(location):
+ return
+ # To know exactly where we find a distribution, we have to feed in the
+ # paths one by one, instead of dumping the list to importlib.metadata.
+ for dist in importlib.metadata.distributions(path=[location]):
+ info_location = get_info_location(dist)
+ try:
+ raw_name = get_dist_name(dist)
+ except BadMetadata as e:
+ logger.warning("Skipping %s due to %s", info_location, e.reason)
+ continue
+ normalized_name = canonicalize_name(raw_name)
+ if normalized_name in self._found_names:
+ continue
+ self._found_names.add(normalized_name)
+ yield dist, info_location
+
+ def find(self, location: str) -> Iterator[BaseDistribution]:
+ """Find distributions in a location.
+
+ The path can be either a directory, or a ZIP archive.
+ """
+ for dist, info_location in self._find_impl(location):
+ if info_location is None:
+ installed_location: Optional[BasePath] = None
+ else:
+ installed_location = info_location.parent
+ yield Distribution(dist, info_location, installed_location)
+
+ def find_linked(self, location: str) -> Iterator[BaseDistribution]:
+ """Read location in egg-link files and return distributions in there.
+
+ The path should be a directory; otherwise this returns nothing. This
+ follows how setuptools does this for compatibility. The first non-empty
+ line in the egg-link is read as a path (resolved against the egg-link's
+ containing directory if relative). Distributions found at that linked
+ location are returned.
+ """
+ path = pathlib.Path(location)
+ if not path.is_dir():
+ return
+ for child in path.iterdir():
+ if child.suffix != ".egg-link":
+ continue
+ with child.open() as f:
+ lines = (line.strip() for line in f)
+ target_rel = next((line for line in lines if line), "")
+ if not target_rel:
+ continue
+ target_location = str(path.joinpath(target_rel))
+ for dist, info_location in self._find_impl(target_location):
+ yield Distribution(dist, info_location, path)
+
+ def _find_eggs_in_dir(self, location: str) -> Iterator[BaseDistribution]:
+ from pip._vendor.pkg_resources import find_distributions
+
+ from pip._internal.metadata import pkg_resources as legacy
+
+ with os.scandir(location) as it:
+ for entry in it:
+ if not entry.name.endswith(".egg"):
+ continue
+ for dist in find_distributions(entry.path):
+ yield legacy.Distribution(dist)
+
+ def _find_eggs_in_zip(self, location: str) -> Iterator[BaseDistribution]:
+ from pip._vendor.pkg_resources import find_eggs_in_zip
+
+ from pip._internal.metadata import pkg_resources as legacy
+
+ try:
+ importer = zipimport.zipimporter(location)
+ except zipimport.ZipImportError:
+ return
+ for dist in find_eggs_in_zip(importer, location):
+ yield legacy.Distribution(dist)
+
+ def find_eggs(self, location: str) -> Iterator[BaseDistribution]:
+ """Find eggs in a location.
+
+ This actually uses the old *pkg_resources* backend. We likely want to
+ deprecate this so we can eventually remove the *pkg_resources*
+ dependency entirely. Before that, this should first emit a deprecation
+ warning for some versions when using the fallback since importing
+ *pkg_resources* is slow for those who don't need it.
+ """
+ if os.path.isdir(location):
+ yield from self._find_eggs_in_dir(location)
+ if zipfile.is_zipfile(location):
+ yield from self._find_eggs_in_zip(location)
+
+
+@functools.lru_cache(maxsize=None) # Warn a distribution exactly once.
+def _emit_egg_deprecation(location: Optional[str]) -> None:
+ deprecated(
+ reason=f"Loading egg at {location} is deprecated.",
+ replacement="to use pip for package installation.",
+ gone_in=None,
+ )
+
+
+class Environment(BaseEnvironment):
+ def __init__(self, paths: Sequence[str]) -> None:
+ self._paths = paths
+
+ @classmethod
+ def default(cls) -> BaseEnvironment:
+ return cls(sys.path)
+
+ @classmethod
+ def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
+ if paths is None:
+ return cls(sys.path)
+ return cls(paths)
+
+ def _iter_distributions(self) -> Iterator[BaseDistribution]:
+ finder = _DistributionFinder()
+ for location in self._paths:
+ yield from finder.find(location)
+ for dist in finder.find_eggs(location):
+ # _emit_egg_deprecation(dist.location) # TODO: Enable this.
+ yield dist
+ # This must go last because that's how pkg_resources tie-breaks.
+ yield from finder.find_linked(location)
+
+ def get_distribution(self, name: str) -> Optional[BaseDistribution]:
+ matches = (
+ distribution
+ for distribution in self.iter_all_distributions()
+ if distribution.canonical_name == canonicalize_name(name)
+ )
+ return next(matches, None)
diff --git a/src/pip/_internal/metadata/pkg_resources.py b/src/pip/_internal/metadata/pkg_resources.py
index e8a8a3807..f330ef12a 100644
--- a/src/pip/_internal/metadata/pkg_resources.py
+++ b/src/pip/_internal/metadata/pkg_resources.py
@@ -1,21 +1,26 @@
import email.message
+import email.parser
import logging
-from typing import Collection, Iterable, Iterator, List, NamedTuple, Optional
+import os
+import zipfile
+from typing import Collection, Iterable, Iterator, List, Mapping, NamedTuple, Optional
from pip._vendor import pkg_resources
from pip._vendor.packaging.requirements import Requirement
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
from pip._vendor.packaging.version import parse as parse_version
-from pip._internal.utils import misc # TODO: Move definition here.
-from pip._internal.utils.packaging import get_installer, get_metadata
-from pip._internal.utils.wheel import pkg_resources_distribution_for_wheel
+from pip._internal.exceptions import InvalidWheel, NoneMetadataError, UnsupportedWheel
+from pip._internal.utils.egg_link import egg_link_path_from_location
+from pip._internal.utils.misc import display_path, normalize_path
+from pip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file
from .base import (
BaseDistribution,
BaseEntryPoint,
BaseEnvironment,
DistributionVersion,
+ InfoPath,
Wheel,
)
@@ -28,14 +33,101 @@ class EntryPoint(NamedTuple):
group: str
+class InMemoryMetadata:
+ """IMetadataProvider that reads metadata files from a dictionary.
+
+ This also maps metadata decoding exceptions to our internal exception type.
+ """
+
+ def __init__(self, metadata: Mapping[str, bytes], wheel_name: str) -> None:
+ self._metadata = metadata
+ self._wheel_name = wheel_name
+
+ def has_metadata(self, name: str) -> bool:
+ return name in self._metadata
+
+ def get_metadata(self, name: str) -> str:
+ try:
+ return self._metadata[name].decode()
+ except UnicodeDecodeError as e:
+ # Augment the default error with the origin of the file.
+ raise UnsupportedWheel(
+ f"Error decoding metadata for {self._wheel_name}: {e} in {name} file"
+ )
+
+ def get_metadata_lines(self, name: str) -> Iterable[str]:
+ return pkg_resources.yield_lines(self.get_metadata(name))
+
+ def metadata_isdir(self, name: str) -> bool:
+ return False
+
+ def metadata_listdir(self, name: str) -> List[str]:
+ return []
+
+ def run_script(self, script_name: str, namespace: str) -> None:
+ pass
+
+
class Distribution(BaseDistribution):
def __init__(self, dist: pkg_resources.Distribution) -> None:
self._dist = dist
@classmethod
- def from_wheel(cls, wheel: Wheel, name: str) -> "Distribution":
- with wheel.as_zipfile() as zf:
- dist = pkg_resources_distribution_for_wheel(zf, name, wheel.location)
+ def from_directory(cls, directory: str) -> BaseDistribution:
+ dist_dir = directory.rstrip(os.sep)
+
+ # Build a PathMetadata object, from path to metadata. :wink:
+ base_dir, dist_dir_name = os.path.split(dist_dir)
+ metadata = pkg_resources.PathMetadata(base_dir, dist_dir)
+
+ # Determine the correct Distribution object type.
+ if dist_dir.endswith(".egg-info"):
+ dist_cls = pkg_resources.Distribution
+ dist_name = os.path.splitext(dist_dir_name)[0]
+ else:
+ assert dist_dir.endswith(".dist-info")
+ dist_cls = pkg_resources.DistInfoDistribution
+ dist_name = os.path.splitext(dist_dir_name)[0].split("-")[0]
+
+ dist = dist_cls(base_dir, project_name=dist_name, metadata=metadata)
+ return cls(dist)
+
+ @classmethod
+ def from_metadata_file_contents(
+ cls,
+ metadata_contents: bytes,
+ filename: str,
+ project_name: str,
+ ) -> BaseDistribution:
+ metadata_dict = {
+ "METADATA": metadata_contents,
+ }
+ dist = pkg_resources.DistInfoDistribution(
+ location=filename,
+ metadata=InMemoryMetadata(metadata_dict, filename),
+ project_name=project_name,
+ )
+ return cls(dist)
+
+ @classmethod
+ def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
+ try:
+ with wheel.as_zipfile() as zf:
+ info_dir, _ = parse_wheel(zf, name)
+ metadata_dict = {
+ path.split("/", 1)[-1]: read_wheel_metadata_file(zf, path)
+ for path in zf.namelist()
+ if path.startswith(f"{info_dir}/")
+ }
+ except zipfile.BadZipFile as e:
+ raise InvalidWheel(wheel.location, name) from e
+ except UnsupportedWheel as e:
+ raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
+ dist = pkg_resources.DistInfoDistribution(
+ location=wheel.location,
+ metadata=InMemoryMetadata(metadata_dict, wheel.location),
+ project_name=name,
+ )
return cls(dist)
@property
@@ -43,10 +135,31 @@ class Distribution(BaseDistribution):
return self._dist.location
@property
- def info_directory(self) -> Optional[str]:
+ def installed_location(self) -> Optional[str]:
+ egg_link = egg_link_path_from_location(self.raw_name)
+ if egg_link:
+ location = egg_link
+ elif self.location:
+ location = self.location
+ else:
+ return None
+ return normalize_path(location)
+
+ @property
+ def info_location(self) -> Optional[str]:
return self._dist.egg_info
@property
+ def installed_by_distutils(self) -> bool:
+ # A distutils-installed distribution is provided by FileMetadata. This
+ # provider has a "path" attribute not present anywhere else. Not the
+ # best introspection logic, but pip has been doing this for a long time.
+ try:
+ return bool(self._dist._provider.path)
+ except AttributeError:
+ return False
+
+ @property
def canonical_name(self) -> NormalizedName:
return canonicalize_name(self._dist.project_name)
@@ -54,26 +167,20 @@ class Distribution(BaseDistribution):
def version(self) -> DistributionVersion:
return parse_version(self._dist.version)
- @property
- def installer(self) -> str:
- return get_installer(self._dist)
-
- @property
- def local(self) -> bool:
- return misc.dist_is_local(self._dist)
+ def is_file(self, path: InfoPath) -> bool:
+ return self._dist.has_metadata(str(path))
- @property
- def in_usersite(self) -> bool:
- return misc.dist_in_usersite(self._dist)
+ def iter_distutils_script_names(self) -> Iterator[str]:
+ yield from self._dist.metadata_listdir("scripts")
- @property
- def in_site_packages(self) -> bool:
- return misc.dist_in_site_packages(self._dist)
-
- def read_text(self, name: str) -> str:
+ def read_text(self, path: InfoPath) -> str:
+ name = str(path)
if not self._dist.has_metadata(name):
raise FileNotFoundError(name)
- return self._dist.get_metadata(name)
+ content = self._dist.get_metadata(name)
+ if content is None:
+ raise NoneMetadataError(self, name)
+ return content
def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
for group, entries in self._dist.get_entry_map().items():
@@ -81,9 +188,27 @@ class Distribution(BaseDistribution):
name, _, value = str(entry_point).partition("=")
yield EntryPoint(name=name.strip(), value=value.strip(), group=group)
- @property
- def metadata(self) -> email.message.Message:
- return get_metadata(self._dist)
+ def _metadata_impl(self) -> email.message.Message:
+ """
+ :raises NoneMetadataError: if the distribution reports `has_metadata()`
+ True but `get_metadata()` returns None.
+ """
+ if isinstance(self._dist, pkg_resources.DistInfoDistribution):
+ metadata_name = "METADATA"
+ else:
+ metadata_name = "PKG-INFO"
+ try:
+ metadata = self.read_text(metadata_name)
+ except FileNotFoundError:
+ if self.location:
+ displaying_path = display_path(self.location)
+ else:
+ displaying_path = repr(self.location)
+ logger.warning("No metadata found in %s", displaying_path)
+ metadata = ""
+ feed_parser = email.parser.FeedParser()
+ feed_parser.feed(metadata)
+ return feed_parser.close()
def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
if extras: # pkg_resources raises on invalid extras, so we sanitize.
@@ -106,6 +231,10 @@ class Environment(BaseEnvironment):
def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
return cls(pkg_resources.WorkingSet(paths))
+ def _iter_distributions(self) -> Iterator[BaseDistribution]:
+ for dist in self._ws:
+ yield Distribution(dist)
+
def _search_distribution(self, name: str) -> Optional[BaseDistribution]:
"""Find a distribution matching the ``name`` in the environment.
@@ -113,13 +242,12 @@ class Environment(BaseEnvironment):
match the behavior of ``pkg_resources.get_distribution()``.
"""
canonical_name = canonicalize_name(name)
- for dist in self.iter_distributions():
+ for dist in self.iter_all_distributions():
if dist.canonical_name == canonical_name:
return dist
return None
def get_distribution(self, name: str) -> Optional[BaseDistribution]:
-
# Search the distribution by looking through the working set.
dist = self._search_distribution(name)
if dist:
@@ -140,7 +268,3 @@ class Environment(BaseEnvironment):
except pkg_resources.DistributionNotFound:
return None
return self._search_distribution(name)
-
- def _iter_distributions(self) -> Iterator[BaseDistribution]:
- for dist in self._ws:
- yield Distribution(dist)
diff --git a/src/pip/_internal/models/direct_url.py b/src/pip/_internal/models/direct_url.py
index 92060d45d..c3de70a74 100644
--- a/src/pip/_internal/models/direct_url.py
+++ b/src/pip/_internal/models/direct_url.py
@@ -74,14 +74,10 @@ class VcsInfo:
vcs: str,
commit_id: str,
requested_revision: Optional[str] = None,
- resolved_revision: Optional[str] = None,
- resolved_revision_type: Optional[str] = None,
) -> None:
self.vcs = vcs
self.requested_revision = requested_revision
self.commit_id = commit_id
- self.resolved_revision = resolved_revision
- self.resolved_revision_type = resolved_revision_type
@classmethod
def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["VcsInfo"]:
@@ -91,8 +87,6 @@ class VcsInfo:
vcs=_get_required(d, str, "vcs"),
commit_id=_get_required(d, str, "commit_id"),
requested_revision=_get(d, str, "requested_revision"),
- resolved_revision=_get(d, str, "resolved_revision"),
- resolved_revision_type=_get(d, str, "resolved_revision_type"),
)
def _to_dict(self) -> Dict[str, Any]:
@@ -100,8 +94,6 @@ class VcsInfo:
vcs=self.vcs,
requested_revision=self.requested_revision,
commit_id=self.commit_id,
- resolved_revision=self.resolved_revision,
- resolved_revision_type=self.resolved_revision_type,
)
@@ -111,17 +103,33 @@ class ArchiveInfo:
def __init__(
self,
hash: Optional[str] = None,
+ hashes: Optional[Dict[str, str]] = None,
) -> None:
+ if hash is not None:
+ # Auto-populate the hashes key to upgrade to the new format automatically.
+ # We don't back-populate the legacy hash key.
+ try:
+ hash_name, hash_value = hash.split("=", 1)
+ except ValueError:
+ raise DirectUrlValidationError(
+ f"invalid archive_info.hash format: {hash!r}"
+ )
+ if hashes is None:
+ hashes = {hash_name: hash_value}
+ elif hash_name not in hash:
+ hashes = hashes.copy()
+ hashes[hash_name] = hash_value
self.hash = hash
+ self.hashes = hashes
@classmethod
def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["ArchiveInfo"]:
if d is None:
return None
- return cls(hash=_get(d, str, "hash"))
+ return cls(hash=_get(d, str, "hash"), hashes=_get(d, dict, "hashes"))
def _to_dict(self) -> Dict[str, Any]:
- return _filter_none(hash=self.hash)
+ return _filter_none(hash=self.hash, hashes=self.hashes)
class DirInfo:
diff --git a/src/pip/_internal/models/installation_report.py b/src/pip/_internal/models/installation_report.py
new file mode 100644
index 000000000..b54afb109
--- /dev/null
+++ b/src/pip/_internal/models/installation_report.py
@@ -0,0 +1,53 @@
+from typing import Any, Dict, Sequence
+
+from pip._vendor.packaging.markers import default_environment
+
+from pip import __version__
+from pip._internal.req.req_install import InstallRequirement
+
+
+class InstallationReport:
+ def __init__(self, install_requirements: Sequence[InstallRequirement]):
+ self._install_requirements = install_requirements
+
+ @classmethod
+ def _install_req_to_dict(cls, ireq: InstallRequirement) -> Dict[str, Any]:
+ assert ireq.download_info, f"No download_info for {ireq}"
+ res = {
+ # PEP 610 json for the download URL. download_info.archive_info.hash may
+ # be absent when the requirement was installed from the wheel cache
+ # and the cache entry was populated by an older pip version that did not
+ # record origin.json.
+ "download_info": ireq.download_info.to_dict(),
+ # is_direct is true if the requirement was a direct URL reference (which
+ # includes editable requirements), and false if the requirement was
+ # downloaded from a PEP 503 index or --find-links.
+ "is_direct": bool(ireq.original_link),
+ # requested is true if the requirement was specified by the user (aka
+ # top level requirement), and false if it was installed as a dependency of a
+ # requirement. https://peps.python.org/pep-0376/#requested
+ "requested": ireq.user_supplied,
+ # PEP 566 json encoding for metadata
+ # https://www.python.org/dev/peps/pep-0566/#json-compatible-metadata
+ "metadata": ireq.get_dist().metadata_dict,
+ }
+ if ireq.user_supplied and ireq.extras:
+ # For top level requirements, the list of requested extras, if any.
+ res["requested_extras"] = list(sorted(ireq.extras))
+ return res
+
+ def to_dict(self) -> Dict[str, Any]:
+ return {
+ "version": "1",
+ "pip_version": __version__,
+ "install": [
+ self._install_req_to_dict(ireq) for ireq in self._install_requirements
+ ],
+ # https://peps.python.org/pep-0508/#environment-markers
+ # TODO: currently, the resolver uses the default environment to evaluate
+ # environment markers, so that is what we report here. In the future, it
+ # should also take into account options such as --python-version or
+ # --platform, perhaps under the form of an environment_override field?
+ # https://github.com/pypa/pip/issues/11198
+ "environment": default_environment(),
+ }
diff --git a/src/pip/_internal/models/link.py b/src/pip/_internal/models/link.py
index 6069b278b..a1e4d5a08 100644
--- a/src/pip/_internal/models/link.py
+++ b/src/pip/_internal/models/link.py
@@ -1,14 +1,28 @@
import functools
+import itertools
import logging
import os
import posixpath
import re
import urllib.parse
-from typing import TYPE_CHECKING, Dict, List, NamedTuple, Optional, Tuple, Union
+from dataclasses import dataclass
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Dict,
+ List,
+ Mapping,
+ NamedTuple,
+ Optional,
+ Tuple,
+ Union,
+)
+from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.filetypes import WHEEL_EXTENSION
from pip._internal.utils.hashes import Hashes
from pip._internal.utils.misc import (
+ pairwise,
redact_auth_from_url,
split_auth_from_netloc,
splitext,
@@ -17,12 +31,130 @@ from pip._internal.utils.models import KeyBasedCompareMixin
from pip._internal.utils.urls import path_to_url, url_to_path
if TYPE_CHECKING:
- from pip._internal.index.collector import HTMLPage
+ from pip._internal.index.collector import IndexContent
logger = logging.getLogger(__name__)
-_SUPPORTED_HASHES = ("sha1", "sha224", "sha384", "sha256", "sha512", "md5")
+# Order matters, earlier hashes have a precedence over later hashes for what
+# we will pick to use.
+_SUPPORTED_HASHES = ("sha512", "sha384", "sha256", "sha224", "sha1", "md5")
+
+
+@dataclass(frozen=True)
+class LinkHash:
+ """Links to content may have embedded hash values. This class parses those.
+
+ `name` must be any member of `_SUPPORTED_HASHES`.
+
+ This class can be converted to and from `ArchiveInfo`. While ArchiveInfo intends to
+ be JSON-serializable to conform to PEP 610, this class contains the logic for
+ parsing a hash name and value for correctness, and then checking whether that hash
+ conforms to a schema with `.is_hash_allowed()`."""
+
+ name: str
+ value: str
+
+ _hash_re = re.compile(
+ # NB: we do not validate that the second group (.*) is a valid hex
+ # digest. Instead, we simply keep that string in this class, and then check it
+ # against Hashes when hash-checking is needed. This is easier to debug than
+ # proactively discarding an invalid hex digest, as we handle incorrect hashes
+ # and malformed hashes in the same place.
+ r"({choices})=(.*)".format(
+ choices="|".join(re.escape(hash_name) for hash_name in _SUPPORTED_HASHES)
+ ),
+ )
+
+ def __post_init__(self) -> None:
+ assert self._hash_re.match(f"{self.name}={self.value}")
+
+ @classmethod
+ @functools.lru_cache(maxsize=None)
+ def split_hash_name_and_value(cls, url: str) -> Optional["LinkHash"]:
+ """Search a string for a checksum algorithm name and encoded output value."""
+ match = cls._hash_re.search(url)
+ if match is None:
+ return None
+ name, value = match.groups()
+ return cls(name=name, value=value)
+
+ def as_dict(self) -> Dict[str, str]:
+ return {self.name: self.value}
+
+ def as_hashes(self) -> Hashes:
+ """Return a Hashes instance which checks only for the current hash."""
+ return Hashes({self.name: [self.value]})
+
+ def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
+ """
+ Return True if the current hash is allowed by `hashes`.
+ """
+ if hashes is None:
+ return False
+ return hashes.is_hash_allowed(self.name, hex_digest=self.value)
+
+
+def _clean_url_path_part(part: str) -> str:
+ """
+ Clean a "part" of a URL path (i.e. after splitting on "@" characters).
+ """
+ # We unquote prior to quoting to make sure nothing is double quoted.
+ return urllib.parse.quote(urllib.parse.unquote(part))
+
+
+def _clean_file_url_path(part: str) -> str:
+ """
+ Clean the first part of a URL path that corresponds to a local
+ filesystem path (i.e. the first part after splitting on "@" characters).
+ """
+ # We unquote prior to quoting to make sure nothing is double quoted.
+ # Also, on Windows the path part might contain a drive letter which
+ # should not be quoted. On Linux where drive letters do not
+ # exist, the colon should be quoted. We rely on urllib.request
+ # to do the right thing here.
+ return urllib.request.pathname2url(urllib.request.url2pathname(part))
+
+
+# percent-encoded: /
+_reserved_chars_re = re.compile("(@|%2F)", re.IGNORECASE)
+
+
+def _clean_url_path(path: str, is_local_path: bool) -> str:
+ """
+ Clean the path portion of a URL.
+ """
+ if is_local_path:
+ clean_func = _clean_file_url_path
+ else:
+ clean_func = _clean_url_path_part
+
+ # Split on the reserved characters prior to cleaning so that
+ # revision strings in VCS URLs are properly preserved.
+ parts = _reserved_chars_re.split(path)
+
+ cleaned_parts = []
+ for to_clean, reserved in pairwise(itertools.chain(parts, [""])):
+ cleaned_parts.append(clean_func(to_clean))
+ # Normalize %xx escapes (e.g. %2f -> %2F)
+ cleaned_parts.append(reserved.upper())
+
+ return "".join(cleaned_parts)
+
+
+def _ensure_quoted_url(url: str) -> str:
+ """
+ Make sure a link is fully quoted.
+ For example, if ' ' occurs in the URL, it will be replaced with "%20",
+ and without double-quoting other characters.
+ """
+ # Split the URL into parts according to the general structure
+ # `scheme://netloc/path;parameters?query#fragment`.
+ result = urllib.parse.urlparse(url)
+ # If the netloc is empty, then the URL refers to a local filesystem path.
+ is_local_path = not result.netloc
+ path = _clean_url_path(result.path, is_local_path=is_local_path)
+ return urllib.parse.urlunparse(result._replace(path=path))
class Link(KeyBasedCompareMixin):
@@ -31,23 +163,28 @@ class Link(KeyBasedCompareMixin):
__slots__ = [
"_parsed_url",
"_url",
+ "_hashes",
"comes_from",
"requires_python",
"yanked_reason",
+ "dist_info_metadata",
"cache_link_parsing",
+ "egg_fragment",
]
def __init__(
self,
url: str,
- comes_from: Optional[Union[str, "HTMLPage"]] = None,
+ comes_from: Optional[Union[str, "IndexContent"]] = None,
requires_python: Optional[str] = None,
yanked_reason: Optional[str] = None,
+ dist_info_metadata: Optional[str] = None,
cache_link_parsing: bool = True,
+ hashes: Optional[Mapping[str, str]] = None,
) -> None:
"""
:param url: url of the resource pointed to (href of the link)
- :param comes_from: instance of HTMLPage where the link was found,
+ :param comes_from: instance of IndexContent where the link was found,
or string.
:param requires_python: String containing the `Requires-Python`
metadata field, specified in PEP 345. This may be specified by
@@ -59,11 +196,16 @@ class Link(KeyBasedCompareMixin):
a simple repository HTML link. If the file has been yanked but
no reason was provided, this should be the empty string. See
PEP 592 for more information and the specification.
+ :param dist_info_metadata: the metadata attached to the file, or None if no such
+ metadata is provided. This is the value of the "data-dist-info-metadata"
+ attribute, if present, in a simple repository HTML link. This may be parsed
+ into its own `Link` by `self.metadata_link()`. See PEP 658 for more
+ information and the specification.
:param cache_link_parsing: A flag that is used elsewhere to determine
- whether resources retrieved from this link
- should be cached. PyPI index urls should
- generally have this set to False, for
- example.
+ whether resources retrieved from this link should be cached. PyPI
+ URLs should generally have this set to False, for example.
+ :param hashes: A mapping of hash names to digests to allow us to
+ determine the validity of a download.
"""
# url can be a UNC windows share
@@ -75,13 +217,84 @@ class Link(KeyBasedCompareMixin):
# trying to set a new value.
self._url = url
+ link_hash = LinkHash.split_hash_name_and_value(url)
+ hashes_from_link = {} if link_hash is None else link_hash.as_dict()
+ if hashes is None:
+ self._hashes = hashes_from_link
+ else:
+ self._hashes = {**hashes, **hashes_from_link}
+
self.comes_from = comes_from
self.requires_python = requires_python if requires_python else None
self.yanked_reason = yanked_reason
+ self.dist_info_metadata = dist_info_metadata
super().__init__(key=url, defining_class=Link)
self.cache_link_parsing = cache_link_parsing
+ self.egg_fragment = self._egg_fragment()
+
+ @classmethod
+ def from_json(
+ cls,
+ file_data: Dict[str, Any],
+ page_url: str,
+ ) -> Optional["Link"]:
+ """
+ Convert an pypi json document from a simple repository page into a Link.
+ """
+ file_url = file_data.get("url")
+ if file_url is None:
+ return None
+
+ url = _ensure_quoted_url(urllib.parse.urljoin(page_url, file_url))
+ pyrequire = file_data.get("requires-python")
+ yanked_reason = file_data.get("yanked")
+ dist_info_metadata = file_data.get("dist-info-metadata")
+ hashes = file_data.get("hashes", {})
+
+ # The Link.yanked_reason expects an empty string instead of a boolean.
+ if yanked_reason and not isinstance(yanked_reason, str):
+ yanked_reason = ""
+ # The Link.yanked_reason expects None instead of False.
+ elif not yanked_reason:
+ yanked_reason = None
+
+ return cls(
+ url,
+ comes_from=page_url,
+ requires_python=pyrequire,
+ yanked_reason=yanked_reason,
+ hashes=hashes,
+ dist_info_metadata=dist_info_metadata,
+ )
+
+ @classmethod
+ def from_element(
+ cls,
+ anchor_attribs: Dict[str, Optional[str]],
+ page_url: str,
+ base_url: str,
+ ) -> Optional["Link"]:
+ """
+ Convert an anchor element's attributes in a simple repository page to a Link.
+ """
+ href = anchor_attribs.get("href")
+ if not href:
+ return None
+
+ url = _ensure_quoted_url(urllib.parse.urljoin(base_url, href))
+ pyrequire = anchor_attribs.get("data-requires-python")
+ yanked_reason = anchor_attribs.get("data-yanked")
+ dist_info_metadata = anchor_attribs.get("data-dist-info-metadata")
+
+ return cls(
+ url,
+ comes_from=page_url,
+ requires_python=pyrequire,
+ yanked_reason=yanked_reason,
+ dist_info_metadata=dist_info_metadata,
+ )
def __str__(self) -> str:
if self.requires_python:
@@ -149,12 +362,28 @@ class Link(KeyBasedCompareMixin):
_egg_fragment_re = re.compile(r"[#&]egg=([^&]*)")
- @property
- def egg_fragment(self) -> Optional[str]:
+ # Per PEP 508.
+ _project_name_re = re.compile(
+ r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
+ )
+
+ def _egg_fragment(self) -> Optional[str]:
match = self._egg_fragment_re.search(self._url)
if not match:
return None
- return match.group(1)
+
+ # An egg fragment looks like a PEP 508 project name, along with
+ # an optional extras specifier. Anything else is invalid.
+ project_name = match.group(1)
+ if not self._project_name_re.match(project_name):
+ deprecated(
+ reason=f"{self} contains an egg fragment with a non-PEP 508 name",
+ replacement="to use the req @ url syntax, and remove the egg fragment",
+ gone_in="25.0",
+ issue=11617,
+ )
+
+ return project_name
_subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)")
@@ -165,23 +394,34 @@ class Link(KeyBasedCompareMixin):
return None
return match.group(1)
- _hash_re = re.compile(
- r"({choices})=([a-f0-9]+)".format(choices="|".join(_SUPPORTED_HASHES))
- )
+ def metadata_link(self) -> Optional["Link"]:
+ """Implementation of PEP 658 parsing."""
+ # Note that Link.from_element() parsing the "data-dist-info-metadata" attribute
+ # from an HTML anchor tag is typically how the Link.dist_info_metadata attribute
+ # gets set.
+ if self.dist_info_metadata is None:
+ return None
+ metadata_url = f"{self.url_without_fragment}.metadata"
+ # If data-dist-info-metadata="true" is set, then the metadata file exists,
+ # but there is no information about its checksum or anything else.
+ if self.dist_info_metadata != "true":
+ link_hash = LinkHash.split_hash_name_and_value(self.dist_info_metadata)
+ else:
+ link_hash = None
+ if link_hash is None:
+ return Link(metadata_url)
+ return Link(metadata_url, hashes=link_hash.as_dict())
+
+ def as_hashes(self) -> Hashes:
+ return Hashes({k: [v] for k, v in self._hashes.items()})
@property
def hash(self) -> Optional[str]:
- match = self._hash_re.search(self._url)
- if match:
- return match.group(2)
- return None
+ return next(iter(self._hashes.values()), None)
@property
def hash_name(self) -> Optional[str]:
- match = self._hash_re.search(self._url)
- if match:
- return match.group(1)
- return None
+ return next(iter(self._hashes), None)
@property
def show_url(self) -> str:
@@ -210,19 +450,15 @@ class Link(KeyBasedCompareMixin):
@property
def has_hash(self) -> bool:
- return self.hash_name is not None
+ return bool(self._hashes)
def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
"""
- Return True if the link has a hash and it is allowed.
+ Return True if the link has a hash and it is allowed by `hashes`.
"""
- if hashes is None or not self.has_hash:
+ if hashes is None:
return False
- # Assert non-None so mypy knows self.hash_name and self.hash are str.
- assert self.hash_name is not None
- assert self.hash is not None
-
- return hashes.is_hash_allowed(self.hash_name, hex_digest=self.hash)
+ return any(hashes.is_hash_allowed(k, v) for k, v in self._hashes.items())
class _CleanResult(NamedTuple):
diff --git a/src/pip/_internal/models/search_scope.py b/src/pip/_internal/models/search_scope.py
index e4e54c2f4..a64af7389 100644
--- a/src/pip/_internal/models/search_scope.py
+++ b/src/pip/_internal/models/search_scope.py
@@ -20,13 +20,14 @@ class SearchScope:
Encapsulates the locations that pip is configured to search.
"""
- __slots__ = ["find_links", "index_urls"]
+ __slots__ = ["find_links", "index_urls", "no_index"]
@classmethod
def create(
cls,
find_links: List[str],
index_urls: List[str],
+ no_index: bool,
) -> "SearchScope":
"""
Create a SearchScope object after normalizing the `find_links`.
@@ -60,15 +61,18 @@ class SearchScope:
return cls(
find_links=built_find_links,
index_urls=index_urls,
+ no_index=no_index,
)
def __init__(
self,
find_links: List[str],
index_urls: List[str],
+ no_index: bool,
) -> None:
self.find_links = find_links
self.index_urls = index_urls
+ self.no_index = no_index
def get_formatted_locations(self) -> str:
lines = []
diff --git a/src/pip/_internal/models/wheel.py b/src/pip/_internal/models/wheel.py
index e09161227..a5dc12bdd 100644
--- a/src/pip/_internal/models/wheel.py
+++ b/src/pip/_internal/models/wheel.py
@@ -13,8 +13,8 @@ class Wheel:
"""A wheel file"""
wheel_file_re = re.compile(
- r"""^(?P<namever>(?P<name>.+?)-(?P<ver>.*?))
- ((-(?P<build>\d[^-]*?))?-(?P<pyver>.+?)-(?P<abi>.+?)-(?P<plat>.+?)
+ r"""^(?P<namever>(?P<name>[^\s-]+?)-(?P<ver>[^\s-]*?))
+ ((-(?P<build>\d[^-]*?))?-(?P<pyver>[^\s-]+?)-(?P<abi>[^\s-]+?)-(?P<plat>[^\s-]+?)
\.whl|\.dist-info)$""",
re.VERBOSE,
)
@@ -58,7 +58,10 @@ class Wheel:
:raises ValueError: If none of the wheel's file tags match one of
the supported tags.
"""
- return min(tags.index(tag) for tag in self.file_tags if tag in tags)
+ try:
+ return next(i for i, t in enumerate(tags) if t in self.file_tags)
+ except StopIteration:
+ raise ValueError()
def find_most_preferred_tag(
self, tags: List[Tag], tag_to_priority: Dict[Tag, int]
diff --git a/src/pip/_internal/network/auth.py b/src/pip/_internal/network/auth.py
index ca42798bd..c0efa765c 100644
--- a/src/pip/_internal/network/auth.py
+++ b/src/pip/_internal/network/auth.py
@@ -3,9 +3,18 @@
Contains interface (MultiDomainBasicAuth) and associated glue code for
providing credentials in the context of network requests.
"""
-
+import logging
+import os
+import shutil
+import subprocess
+import sysconfig
+import typing
import urllib.parse
-from typing import Any, Dict, List, Optional, Tuple
+from abc import ABC, abstractmethod
+from functools import lru_cache
+from os.path import commonprefix
+from pathlib import Path
+from typing import Any, Dict, List, NamedTuple, Optional, Tuple
from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
from pip._vendor.requests.models import Request, Response
@@ -23,59 +32,204 @@ from pip._internal.vcs.versioncontrol import AuthInfo
logger = getLogger(__name__)
-Credentials = Tuple[str, str, str]
+KEYRING_DISABLED = False
+
+
+class Credentials(NamedTuple):
+ url: str
+ username: str
+ password: str
+
+
+class KeyRingBaseProvider(ABC):
+ """Keyring base provider interface"""
+
+ has_keyring: bool
+
+ @abstractmethod
+ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
+ ...
+
+ @abstractmethod
+ def save_auth_info(self, url: str, username: str, password: str) -> None:
+ ...
+
-try:
- import keyring
-except ImportError:
- keyring = None # type: ignore[assignment]
-except Exception as exc:
- logger.warning(
- "Keyring is skipped due to an exception: %s",
- str(exc),
- )
- keyring = None # type: ignore[assignment]
+class KeyRingNullProvider(KeyRingBaseProvider):
+ """Keyring null provider"""
+ has_keyring = False
-def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[AuthInfo]:
- """Return the tuple auth for a given url from keyring."""
- global keyring
- if not url or not keyring:
+ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
return None
- try:
- try:
- get_credential = keyring.get_credential
- except AttributeError:
- pass
- else:
+ def save_auth_info(self, url: str, username: str, password: str) -> None:
+ return None
+
+
+class KeyRingPythonProvider(KeyRingBaseProvider):
+ """Keyring interface which uses locally imported `keyring`"""
+
+ has_keyring = True
+
+ def __init__(self) -> None:
+ import keyring
+
+ self.keyring = keyring
+
+ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
+ # Support keyring's get_credential interface which supports getting
+ # credentials without a username. This is only available for
+ # keyring>=15.2.0.
+ if hasattr(self.keyring, "get_credential"):
logger.debug("Getting credentials from keyring for %s", url)
- cred = get_credential(url, username)
+ cred = self.keyring.get_credential(url, username)
if cred is not None:
return cred.username, cred.password
return None
- if username:
+ if username is not None:
logger.debug("Getting password from keyring for %s", url)
- password = keyring.get_password(url, username)
+ password = self.keyring.get_password(url, username)
if password:
return username, password
+ return None
+
+ def save_auth_info(self, url: str, username: str, password: str) -> None:
+ self.keyring.set_password(url, username, password)
+
+
+class KeyRingCliProvider(KeyRingBaseProvider):
+ """Provider which uses `keyring` cli
+
+ Instead of calling the keyring package installed alongside pip
+ we call keyring on the command line which will enable pip to
+ use which ever installation of keyring is available first in
+ PATH.
+ """
+
+ has_keyring = True
+
+ def __init__(self, cmd: str) -> None:
+ self.keyring = cmd
+
+ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
+ # This is the default implementation of keyring.get_credential
+ # https://github.com/jaraco/keyring/blob/97689324abcf01bd1793d49063e7ca01e03d7d07/keyring/backend.py#L134-L139
+ if username is not None:
+ password = self._get_password(url, username)
+ if password is not None:
+ return username, password
+ return None
+
+ def save_auth_info(self, url: str, username: str, password: str) -> None:
+ return self._set_password(url, username, password)
+
+ def _get_password(self, service_name: str, username: str) -> Optional[str]:
+ """Mirror the implementation of keyring.get_password using cli"""
+ if self.keyring is None:
+ return None
+
+ cmd = [self.keyring, "get", service_name, username]
+ env = os.environ.copy()
+ env["PYTHONIOENCODING"] = "utf-8"
+ res = subprocess.run(
+ cmd,
+ stdin=subprocess.DEVNULL,
+ stdout=subprocess.PIPE,
+ env=env,
+ )
+ if res.returncode:
+ return None
+ return res.stdout.decode("utf-8").strip(os.linesep)
- except Exception as exc:
- logger.warning(
- "Keyring is skipped due to an exception: %s",
- str(exc),
+ def _set_password(self, service_name: str, username: str, password: str) -> None:
+ """Mirror the implementation of keyring.set_password using cli"""
+ if self.keyring is None:
+ return None
+ env = os.environ.copy()
+ env["PYTHONIOENCODING"] = "utf-8"
+ subprocess.run(
+ [self.keyring, "set", service_name, username],
+ input=f"{password}{os.linesep}".encode("utf-8"),
+ env=env,
+ check=True,
)
- keyring = None # type: ignore[assignment]
- return None
+ return None
+
+
+@lru_cache(maxsize=None)
+def get_keyring_provider(provider: str) -> KeyRingBaseProvider:
+ logger.verbose("Keyring provider requested: %s", provider)
+
+ # keyring has previously failed and been disabled
+ if KEYRING_DISABLED:
+ provider = "disabled"
+ if provider in ["import", "auto"]:
+ try:
+ impl = KeyRingPythonProvider()
+ logger.verbose("Keyring provider set: import")
+ return impl
+ except ImportError:
+ pass
+ except Exception as exc:
+ # In the event of an unexpected exception
+ # we should warn the user
+ msg = "Installed copy of keyring fails with exception %s"
+ if provider == "auto":
+ msg = msg + ", trying to find a keyring executable as a fallback"
+ logger.warning(msg, exc, exc_info=logger.isEnabledFor(logging.DEBUG))
+ if provider in ["subprocess", "auto"]:
+ cli = shutil.which("keyring")
+ if cli and cli.startswith(sysconfig.get_path("scripts")):
+ # all code within this function is stolen from shutil.which implementation
+ @typing.no_type_check
+ def PATH_as_shutil_which_determines_it() -> str:
+ path = os.environ.get("PATH", None)
+ if path is None:
+ try:
+ path = os.confstr("CS_PATH")
+ except (AttributeError, ValueError):
+ # os.confstr() or CS_PATH is not available
+ path = os.defpath
+ # bpo-35755: Don't use os.defpath if the PATH environment variable is
+ # set to an empty string
+
+ return path
+
+ scripts = Path(sysconfig.get_path("scripts"))
+
+ paths = []
+ for path in PATH_as_shutil_which_determines_it().split(os.pathsep):
+ p = Path(path)
+ try:
+ if not p.samefile(scripts):
+ paths.append(path)
+ except FileNotFoundError:
+ pass
+
+ path = os.pathsep.join(paths)
+
+ cli = shutil.which("keyring", path=path)
+
+ if cli:
+ logger.verbose("Keyring provider set: subprocess with executable %s", cli)
+ return KeyRingCliProvider(cli)
+
+ logger.verbose("Keyring provider set: disabled")
+ return KeyRingNullProvider()
class MultiDomainBasicAuth(AuthBase):
def __init__(
- self, prompting: bool = True, index_urls: Optional[List[str]] = None
+ self,
+ prompting: bool = True,
+ index_urls: Optional[List[str]] = None,
+ keyring_provider: str = "auto",
) -> None:
self.prompting = prompting
self.index_urls = index_urls
+ self.keyring_provider = keyring_provider # type: ignore[assignment]
self.passwords: Dict[str, AuthInfo] = {}
# When the user is prompted to enter credentials and keyring is
# available, we will offer to save them. If the user accepts,
@@ -84,6 +238,47 @@ class MultiDomainBasicAuth(AuthBase):
# ``save_credentials`` to save these.
self._credentials_to_save: Optional[Credentials] = None
+ @property
+ def keyring_provider(self) -> KeyRingBaseProvider:
+ return get_keyring_provider(self._keyring_provider)
+
+ @keyring_provider.setter
+ def keyring_provider(self, provider: str) -> None:
+ # The free function get_keyring_provider has been decorated with
+ # functools.cache. If an exception occurs in get_keyring_auth that
+ # cache will be cleared and keyring disabled, take that into account
+ # if you want to remove this indirection.
+ self._keyring_provider = provider
+
+ @property
+ def use_keyring(self) -> bool:
+ # We won't use keyring when --no-input is passed unless
+ # a specific provider is requested because it might require
+ # user interaction
+ return self.prompting or self._keyring_provider not in ["auto", "disabled"]
+
+ def _get_keyring_auth(
+ self,
+ url: Optional[str],
+ username: Optional[str],
+ ) -> Optional[AuthInfo]:
+ """Return the tuple auth for a given url from keyring."""
+ # Do nothing if no url was provided
+ if not url:
+ return None
+
+ try:
+ return self.keyring_provider.get_auth_info(url, username)
+ except Exception as exc:
+ logger.warning(
+ "Keyring is skipped due to an exception: %s",
+ str(exc),
+ )
+ global KEYRING_DISABLED
+ KEYRING_DISABLED = True
+ get_keyring_provider.cache_clear()
+ return None
+
def _get_index_url(self, url: str) -> Optional[str]:
"""Return the original index URL matching the requested URL.
@@ -100,15 +295,42 @@ class MultiDomainBasicAuth(AuthBase):
if not url or not self.index_urls:
return None
- for u in self.index_urls:
- prefix = remove_auth_from_url(u).rstrip("/") + "/"
- if url.startswith(prefix):
- return u
- return None
+ url = remove_auth_from_url(url).rstrip("/") + "/"
+ parsed_url = urllib.parse.urlsplit(url)
+
+ candidates = []
+
+ for index in self.index_urls:
+ index = index.rstrip("/") + "/"
+ parsed_index = urllib.parse.urlsplit(remove_auth_from_url(index))
+ if parsed_url == parsed_index:
+ return index
+
+ if parsed_url.netloc != parsed_index.netloc:
+ continue
+
+ candidate = urllib.parse.urlsplit(index)
+ candidates.append(candidate)
+
+ if not candidates:
+ return None
+
+ candidates.sort(
+ reverse=True,
+ key=lambda candidate: commonprefix(
+ [
+ parsed_url.path,
+ candidate.path,
+ ]
+ ).rfind("/"),
+ )
+
+ return urllib.parse.urlunsplit(candidates[0])
def _get_new_credentials(
self,
original_url: str,
+ *,
allow_netrc: bool = True,
allow_keyring: bool = False,
) -> AuthInfo:
@@ -152,8 +374,8 @@ class MultiDomainBasicAuth(AuthBase):
# The index url is more specific than the netloc, so try it first
# fmt: off
kr_auth = (
- get_keyring_auth(index_url, username) or
- get_keyring_auth(netloc, username)
+ self._get_keyring_auth(index_url, username) or
+ self._get_keyring_auth(netloc, username)
)
# fmt: on
if kr_auth:
@@ -230,18 +452,23 @@ class MultiDomainBasicAuth(AuthBase):
def _prompt_for_password(
self, netloc: str
) -> Tuple[Optional[str], Optional[str], bool]:
- username = ask_input(f"User for {netloc}: ")
+ username = ask_input(f"User for {netloc}: ") if self.prompting else None
if not username:
return None, None, False
- auth = get_keyring_auth(netloc, username)
- if auth and auth[0] is not None and auth[1] is not None:
- return auth[0], auth[1], False
+ if self.use_keyring:
+ auth = self._get_keyring_auth(netloc, username)
+ if auth and auth[0] is not None and auth[1] is not None:
+ return auth[0], auth[1], False
password = ask_password("Password: ")
return username, password, True
# Factored out to allow for easy patching in tests
def _should_save_password_to_keyring(self) -> bool:
- if not keyring:
+ if (
+ not self.prompting
+ or not self.use_keyring
+ or not self.keyring_provider.has_keyring
+ ):
return False
return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y"
@@ -251,19 +478,22 @@ class MultiDomainBasicAuth(AuthBase):
if resp.status_code != 401:
return resp
+ username, password = None, None
+
+ # Query the keyring for credentials:
+ if self.use_keyring:
+ username, password = self._get_new_credentials(
+ resp.url,
+ allow_netrc=False,
+ allow_keyring=True,
+ )
+
# We are not able to prompt the user so simply return the response
- if not self.prompting:
+ if not self.prompting and not username and not password:
return resp
parsed = urllib.parse.urlparse(resp.url)
- # Query the keyring for credentials:
- username, password = self._get_new_credentials(
- resp.url,
- allow_netrc=False,
- allow_keyring=True,
- )
-
# Prompt the user for a new username and password
save = False
if not username and not password:
@@ -276,7 +506,11 @@ class MultiDomainBasicAuth(AuthBase):
# Prompt to save the password to keyring
if save and self._should_save_password_to_keyring():
- self._credentials_to_save = (parsed.netloc, username, password)
+ self._credentials_to_save = Credentials(
+ url=parsed.netloc,
+ username=username,
+ password=password,
+ )
# Consume content and release the original connection to allow our new
# request to reuse the same one.
@@ -309,15 +543,17 @@ class MultiDomainBasicAuth(AuthBase):
def save_credentials(self, resp: Response, **kwargs: Any) -> None:
"""Response callback to save credentials on success."""
- assert keyring is not None, "should never reach here without keyring"
- if not keyring:
- return
+ assert (
+ self.keyring_provider.has_keyring
+ ), "should never reach here without keyring"
creds = self._credentials_to_save
self._credentials_to_save = None
if creds and resp.status_code < 400:
try:
logger.info("Saving credentials to keyring")
- keyring.set_password(*creds)
+ self.keyring_provider.save_auth_info(
+ creds.url, creds.username, creds.password
+ )
except Exception:
logger.exception("Failed to save credentials")
diff --git a/src/pip/_internal/network/cache.py b/src/pip/_internal/network/cache.py
index 2d915e6fc..a81a23985 100644
--- a/src/pip/_internal/network/cache.py
+++ b/src/pip/_internal/network/cache.py
@@ -3,7 +3,7 @@
import os
from contextlib import contextmanager
-from typing import Iterator, Optional
+from typing import Generator, Optional
from pip._vendor.cachecontrol.cache import BaseCache
from pip._vendor.cachecontrol.caches import FileCache
@@ -18,7 +18,7 @@ def is_from_cache(response: Response) -> bool:
@contextmanager
-def suppressed_cache_errors() -> Iterator[None]:
+def suppressed_cache_errors() -> Generator[None, None, None]:
"""If we can't access the cache then we can just skip caching and process
requests as if caching wasn't enabled.
"""
@@ -53,7 +53,7 @@ class SafeFileCache(BaseCache):
with open(path, "rb") as f:
return f.read()
- def set(self, key: str, value: bytes) -> None:
+ def set(self, key: str, value: bytes, expires: Optional[int] = None) -> None:
path = self._get_cache_path(key)
with suppressed_cache_errors():
ensure_dir(os.path.dirname(path))
diff --git a/src/pip/_internal/network/download.py b/src/pip/_internal/network/download.py
index 47af547d6..79b82a570 100644
--- a/src/pip/_internal/network/download.py
+++ b/src/pip/_internal/network/download.py
@@ -1,6 +1,6 @@
"""Download files with progress indicators.
"""
-import cgi
+import email.message
import logging
import mimetypes
import os
@@ -8,7 +8,7 @@ from typing import Iterable, Optional, Tuple
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
-from pip._internal.cli.progress_bars import DownloadProgressProvider
+from pip._internal.cli.progress_bars import get_download_progress_renderer
from pip._internal.exceptions import NetworkConnectionError
from pip._internal.models.index import PyPI
from pip._internal.models.link import Link
@@ -65,7 +65,8 @@ def _prepare_download(
if not show_progress:
return chunks
- return DownloadProgressProvider(progress_bar, max=total_length)(chunks)
+ renderer = get_download_progress_renderer(bar_type=progress_bar, size=total_length)
+ return renderer(chunks)
def sanitize_content_filename(filename: str) -> str:
@@ -80,12 +81,13 @@ def parse_content_disposition(content_disposition: str, default_filename: str) -
Parse the "filename" value from a Content-Disposition header, and
return the default filename if the result is empty.
"""
- _type, params = cgi.parse_header(content_disposition)
- filename = params.get("filename")
+ m = email.message.Message()
+ m["content-type"] = content_disposition
+ filename = m.get_param("filename")
if filename:
# We need to sanitize the filename to prevent directory traversal
# in case the filename contains ".." path parts.
- filename = sanitize_content_filename(filename)
+ filename = sanitize_content_filename(str(filename))
return filename or default_filename
diff --git a/src/pip/_internal/network/lazy_wheel.py b/src/pip/_internal/network/lazy_wheel.py
index c9e44d5be..82ec50d51 100644
--- a/src/pip/_internal/network/lazy_wheel.py
+++ b/src/pip/_internal/network/lazy_wheel.py
@@ -5,8 +5,8 @@ __all__ = ["HTTPRangeRequestUnsupported", "dist_from_wheel_url"]
from bisect import bisect_left, bisect_right
from contextlib import contextmanager
from tempfile import NamedTemporaryFile
-from typing import Any, Dict, Iterator, List, Optional, Tuple
-from zipfile import BadZipfile, ZipFile
+from typing import Any, Dict, Generator, List, Optional, Tuple
+from zipfile import BadZipFile, ZipFile
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
@@ -23,7 +23,7 @@ class HTTPRangeRequestUnsupported(Exception):
def dist_from_wheel_url(name: str, url: str, session: PipSession) -> BaseDistribution:
"""Return a distribution object from the given wheel URL.
- This uses HTTP range requests to only fetch the potion of the wheel
+ This uses HTTP range requests to only fetch the portion of the wheel
containing metadata, just enough for the object to be constructed.
If such requests are not supported, HTTPRangeRequestUnsupported
is raised.
@@ -135,11 +135,11 @@ class LazyZipOverHTTP:
self._file.__enter__()
return self
- def __exit__(self, *exc: Any) -> Optional[bool]:
- return self._file.__exit__(*exc)
+ def __exit__(self, *exc: Any) -> None:
+ self._file.__exit__(*exc)
@contextmanager
- def _stay(self) -> Iterator[None]:
+ def _stay(self) -> Generator[None, None, None]:
"""Return a context manager keeping the position.
At the end of the block, seek back to original position.
@@ -160,7 +160,7 @@ class LazyZipOverHTTP:
# For read-only ZIP files, ZipFile only needs
# methods read, seek, seekable and tell.
ZipFile(self) # type: ignore
- except BadZipfile:
+ except BadZipFile:
pass
else:
break
@@ -177,8 +177,8 @@ class LazyZipOverHTTP:
def _merge(
self, start: int, end: int, left: int, right: int
- ) -> Iterator[Tuple[int, int]]:
- """Return an iterator of intervals to be fetched.
+ ) -> Generator[Tuple[int, int], None, None]:
+ """Return a generator of intervals to be fetched.
Args:
start (int): Start of needed interval
diff --git a/src/pip/_internal/network/session.py b/src/pip/_internal/network/session.py
index cbe743ba6..e512ac784 100644
--- a/src/pip/_internal/network/session.py
+++ b/src/pip/_internal/network/session.py
@@ -15,11 +15,23 @@ import subprocess
import sys
import urllib.parse
import warnings
-from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Tuple, Union
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Dict,
+ Generator,
+ List,
+ Mapping,
+ Optional,
+ Sequence,
+ Tuple,
+ Union,
+)
from pip._vendor import requests, urllib3
-from pip._vendor.cachecontrol import CacheControlAdapter
-from pip._vendor.requests.adapters import BaseAdapter, HTTPAdapter
+from pip._vendor.cachecontrol import CacheControlAdapter as _BaseCacheControlAdapter
+from pip._vendor.requests.adapters import DEFAULT_POOLBLOCK, BaseAdapter
+from pip._vendor.requests.adapters import HTTPAdapter as _BaseHTTPAdapter
from pip._vendor.requests.models import PreparedRequest, Response
from pip._vendor.requests.structures import CaseInsensitiveDict
from pip._vendor.urllib3.connectionpool import ConnectionPool
@@ -37,6 +49,12 @@ from pip._internal.utils.glibc import libc_ver
from pip._internal.utils.misc import build_url_from_netloc, parse_netloc
from pip._internal.utils.urls import url_to_path
+if TYPE_CHECKING:
+ from ssl import SSLContext
+
+ from pip._vendor.urllib3.poolmanager import PoolManager
+
+
logger = logging.getLogger(__name__)
SecureOrigin = Tuple[str, str, Optional[Union[int, str]]]
@@ -233,6 +251,48 @@ class LocalFSAdapter(BaseAdapter):
pass
+class _SSLContextAdapterMixin:
+ """Mixin to add the ``ssl_context`` constructor argument to HTTP adapters.
+
+ The additional argument is forwarded directly to the pool manager. This allows us
+ to dynamically decide what SSL store to use at runtime, which is used to implement
+ the optional ``truststore`` backend.
+ """
+
+ def __init__(
+ self,
+ *,
+ ssl_context: Optional["SSLContext"] = None,
+ **kwargs: Any,
+ ) -> None:
+ self._ssl_context = ssl_context
+ super().__init__(**kwargs)
+
+ def init_poolmanager(
+ self,
+ connections: int,
+ maxsize: int,
+ block: bool = DEFAULT_POOLBLOCK,
+ **pool_kwargs: Any,
+ ) -> "PoolManager":
+ if self._ssl_context is not None:
+ pool_kwargs.setdefault("ssl_context", self._ssl_context)
+ return super().init_poolmanager( # type: ignore[misc]
+ connections=connections,
+ maxsize=maxsize,
+ block=block,
+ **pool_kwargs,
+ )
+
+
+class HTTPAdapter(_SSLContextAdapterMixin, _BaseHTTPAdapter):
+ pass
+
+
+class CacheControlAdapter(_SSLContextAdapterMixin, _BaseCacheControlAdapter):
+ pass
+
+
class InsecureHTTPAdapter(HTTPAdapter):
def cert_verify(
self,
@@ -266,6 +326,7 @@ class PipSession(requests.Session):
cache: Optional[str] = None,
trusted_hosts: Sequence[str] = (),
index_urls: Optional[List[str]] = None,
+ ssl_context: Optional["SSLContext"] = None,
**kwargs: Any,
) -> None:
"""
@@ -318,13 +379,14 @@ class PipSession(requests.Session):
secure_adapter = CacheControlAdapter(
cache=SafeFileCache(cache),
max_retries=retries,
+ ssl_context=ssl_context,
)
self._trusted_host_adapter = InsecureCacheControlAdapter(
cache=SafeFileCache(cache),
max_retries=retries,
)
else:
- secure_adapter = HTTPAdapter(max_retries=retries)
+ secure_adapter = HTTPAdapter(max_retries=retries, ssl_context=ssl_context)
self._trusted_host_adapter = insecure_adapter
self.mount("https://", secure_adapter)
@@ -374,7 +436,7 @@ class PipSession(requests.Session):
# Mount wildcard ports for the same host.
self.mount(build_url_from_netloc(host) + ":", self._trusted_host_adapter)
- def iter_secure_origins(self) -> Iterator[SecureOrigin]:
+ def iter_secure_origins(self) -> Generator[SecureOrigin, None, None]:
yield from SECURE_ORIGINS
for host, port in self.pip_trusted_origins:
yield ("*", host, "*" if port is None else port)
@@ -403,7 +465,7 @@ class PipSession(requests.Session):
continue
try:
- addr = ipaddress.ip_address(origin_host)
+ addr = ipaddress.ip_address(origin_host or "")
network = ipaddress.ip_network(secure_host)
except ValueError:
# We don't have both a valid address or a valid network, so
@@ -449,6 +511,8 @@ class PipSession(requests.Session):
def request(self, method: str, url: str, *args: Any, **kwargs: Any) -> Response:
# Allow setting a default timeout on a session
kwargs.setdefault("timeout", self.timeout)
+ # Allow setting a default proxies on a session
+ kwargs.setdefault("proxies", self.proxies)
# Dispatch the actual request
return super().request(method, url, *args, **kwargs)
diff --git a/src/pip/_internal/network/utils.py b/src/pip/_internal/network/utils.py
index 094cf1b4a..134848ae5 100644
--- a/src/pip/_internal/network/utils.py
+++ b/src/pip/_internal/network/utils.py
@@ -1,4 +1,4 @@
-from typing import Dict, Iterator
+from typing import Dict, Generator
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
@@ -56,7 +56,7 @@ def raise_for_status(resp: Response) -> None:
def response_chunks(
response: Response, chunk_size: int = CONTENT_CHUNK_SIZE
-) -> Iterator[bytes]:
+) -> Generator[bytes, None, None]:
"""Given a requests Response, provide the data chunks."""
try:
# Special case for urllib3.
diff --git a/src/pip/_internal/req/req_tracker.py b/src/pip/_internal/operations/build/build_tracker.py
index 24d3c5303..6621549b8 100644
--- a/src/pip/_internal/req/req_tracker.py
+++ b/src/pip/_internal/operations/build/build_tracker.py
@@ -3,7 +3,7 @@ import hashlib
import logging
import os
from types import TracebackType
-from typing import Dict, Iterator, Optional, Set, Type, Union
+from typing import Dict, Generator, Optional, Set, Type, Union
from pip._internal.models.link import Link
from pip._internal.req.req_install import InstallRequirement
@@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
@contextlib.contextmanager
-def update_env_context_manager(**changes: str) -> Iterator[None]:
+def update_env_context_manager(**changes: str) -> Generator[None, None, None]:
target = os.environ
# Save values from the target and change them.
@@ -39,25 +39,25 @@ def update_env_context_manager(**changes: str) -> Iterator[None]:
@contextlib.contextmanager
-def get_requirement_tracker() -> Iterator["RequirementTracker"]:
- root = os.environ.get("PIP_REQ_TRACKER")
+def get_build_tracker() -> Generator["BuildTracker", None, None]:
+ root = os.environ.get("PIP_BUILD_TRACKER")
with contextlib.ExitStack() as ctx:
if root is None:
- root = ctx.enter_context(TempDirectory(kind="req-tracker")).path
- ctx.enter_context(update_env_context_manager(PIP_REQ_TRACKER=root))
+ root = ctx.enter_context(TempDirectory(kind="build-tracker")).path
+ ctx.enter_context(update_env_context_manager(PIP_BUILD_TRACKER=root))
logger.debug("Initialized build tracking at %s", root)
- with RequirementTracker(root) as tracker:
+ with BuildTracker(root) as tracker:
yield tracker
-class RequirementTracker:
+class BuildTracker:
def __init__(self, root: str) -> None:
self._root = root
self._entries: Set[InstallRequirement] = set()
logger.debug("Created build tracker: %s", self._root)
- def __enter__(self) -> "RequirementTracker":
+ def __enter__(self) -> "BuildTracker":
logger.debug("Entered build tracker: %s", self._root)
return self
@@ -118,7 +118,7 @@ class RequirementTracker:
logger.debug("Removed build tracker: %r", self._root)
@contextlib.contextmanager
- def track(self, req: InstallRequirement) -> Iterator[None]:
+ def track(self, req: InstallRequirement) -> Generator[None, None, None]:
self.add(req)
yield
self.remove(req)
diff --git a/src/pip/_internal/operations/build/metadata.py b/src/pip/_internal/operations/build/metadata.py
index 7d12438d6..c66ac354d 100644
--- a/src/pip/_internal/operations/build/metadata.py
+++ b/src/pip/_internal/operations/build/metadata.py
@@ -3,14 +3,20 @@
import os
-from pip._vendor.pep517.wrappers import Pep517HookCaller
+from pip._vendor.pyproject_hooks import BuildBackendHookCaller
from pip._internal.build_env import BuildEnvironment
+from pip._internal.exceptions import (
+ InstallationSubprocessError,
+ MetadataGenerationFailed,
+)
from pip._internal.utils.subprocess import runner_with_spinner_message
from pip._internal.utils.temp_dir import TempDirectory
-def generate_metadata(build_env: BuildEnvironment, backend: Pep517HookCaller) -> str:
+def generate_metadata(
+ build_env: BuildEnvironment, backend: BuildBackendHookCaller, details: str
+) -> str:
"""Generate metadata using mechanisms described in PEP 517.
Returns the generated metadata directory.
@@ -20,11 +26,14 @@ def generate_metadata(build_env: BuildEnvironment, backend: Pep517HookCaller) ->
metadata_dir = metadata_tmpdir.path
with build_env:
- # Note that Pep517HookCaller implements a fallback for
+ # Note that BuildBackendHookCaller implements a fallback for
# prepare_metadata_for_build_wheel, so we don't have to
# consider the possibility that this hook doesn't exist.
runner = runner_with_spinner_message("Preparing metadata (pyproject.toml)")
with backend.subprocess_runner(runner):
- distinfo_dir = backend.prepare_metadata_for_build_wheel(metadata_dir)
+ try:
+ distinfo_dir = backend.prepare_metadata_for_build_wheel(metadata_dir)
+ except InstallationSubprocessError as error:
+ raise MetadataGenerationFailed(package_details=details) from error
return os.path.join(metadata_dir, distinfo_dir)
diff --git a/src/pip/_internal/operations/build/metadata_editable.py b/src/pip/_internal/operations/build/metadata_editable.py
index 13de75f6c..27c69f0d1 100644
--- a/src/pip/_internal/operations/build/metadata_editable.py
+++ b/src/pip/_internal/operations/build/metadata_editable.py
@@ -3,15 +3,19 @@
import os
-from pip._vendor.pep517.wrappers import Pep517HookCaller
+from pip._vendor.pyproject_hooks import BuildBackendHookCaller
from pip._internal.build_env import BuildEnvironment
+from pip._internal.exceptions import (
+ InstallationSubprocessError,
+ MetadataGenerationFailed,
+)
from pip._internal.utils.subprocess import runner_with_spinner_message
from pip._internal.utils.temp_dir import TempDirectory
def generate_editable_metadata(
- build_env: BuildEnvironment, backend: Pep517HookCaller
+ build_env: BuildEnvironment, backend: BuildBackendHookCaller, details: str
) -> str:
"""Generate metadata using mechanisms described in PEP 660.
@@ -22,13 +26,16 @@ def generate_editable_metadata(
metadata_dir = metadata_tmpdir.path
with build_env:
- # Note that Pep517HookCaller implements a fallback for
+ # Note that BuildBackendHookCaller implements a fallback for
# prepare_metadata_for_build_wheel/editable, so we don't have to
# consider the possibility that this hook doesn't exist.
runner = runner_with_spinner_message(
"Preparing editable metadata (pyproject.toml)"
)
with backend.subprocess_runner(runner):
- distinfo_dir = backend.prepare_metadata_for_build_editable(metadata_dir)
+ try:
+ distinfo_dir = backend.prepare_metadata_for_build_editable(metadata_dir)
+ except InstallationSubprocessError as error:
+ raise MetadataGenerationFailed(package_details=details) from error
return os.path.join(metadata_dir, distinfo_dir)
diff --git a/src/pip/_internal/operations/build/metadata_legacy.py b/src/pip/_internal/operations/build/metadata_legacy.py
index ff52de9c4..e60988d64 100644
--- a/src/pip/_internal/operations/build/metadata_legacy.py
+++ b/src/pip/_internal/operations/build/metadata_legacy.py
@@ -6,7 +6,11 @@ import os
from pip._internal.build_env import BuildEnvironment
from pip._internal.cli.spinners import open_spinner
-from pip._internal.exceptions import InstallationError
+from pip._internal.exceptions import (
+ InstallationError,
+ InstallationSubprocessError,
+ MetadataGenerationFailed,
+)
from pip._internal.utils.setuptools_build import make_setuptools_egg_info_args
from pip._internal.utils.subprocess import call_subprocess
from pip._internal.utils.temp_dir import TempDirectory
@@ -56,12 +60,15 @@ def generate_metadata(
with build_env:
with open_spinner("Preparing metadata (setup.py)") as spinner:
- call_subprocess(
- args,
- cwd=source_dir,
- command_desc="python setup.py egg_info",
- spinner=spinner,
- )
+ try:
+ call_subprocess(
+ args,
+ cwd=source_dir,
+ command_desc="python setup.py egg_info",
+ spinner=spinner,
+ )
+ except InstallationSubprocessError as error:
+ raise MetadataGenerationFailed(package_details=details) from error
# Return the .egg-info directory.
return _find_egg_info(egg_info_dir)
diff --git a/src/pip/_internal/operations/build/wheel.py b/src/pip/_internal/operations/build/wheel.py
index b0d2fc9ea..064811ad1 100644
--- a/src/pip/_internal/operations/build/wheel.py
+++ b/src/pip/_internal/operations/build/wheel.py
@@ -2,7 +2,7 @@ import logging
import os
from typing import Optional
-from pip._vendor.pep517.wrappers import Pep517HookCaller
+from pip._vendor.pyproject_hooks import BuildBackendHookCaller
from pip._internal.utils.subprocess import runner_with_spinner_message
@@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
def build_wheel_pep517(
name: str,
- backend: Pep517HookCaller,
+ backend: BuildBackendHookCaller,
metadata_directory: str,
tempd: str,
) -> Optional[str]:
diff --git a/src/pip/_internal/operations/build/wheel_editable.py b/src/pip/_internal/operations/build/wheel_editable.py
index cf7b01aed..719d69dd8 100644
--- a/src/pip/_internal/operations/build/wheel_editable.py
+++ b/src/pip/_internal/operations/build/wheel_editable.py
@@ -2,7 +2,7 @@ import logging
import os
from typing import Optional
-from pip._vendor.pep517.wrappers import HookMissing, Pep517HookCaller
+from pip._vendor.pyproject_hooks import BuildBackendHookCaller, HookMissing
from pip._internal.utils.subprocess import runner_with_spinner_message
@@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
def build_wheel_editable(
name: str,
- backend: Pep517HookCaller,
+ backend: BuildBackendHookCaller,
metadata_directory: str,
tempd: str,
) -> Optional[str]:
diff --git a/src/pip/_internal/operations/build/wheel_legacy.py b/src/pip/_internal/operations/build/wheel_legacy.py
index 2d5cb264e..c5f0492cc 100644
--- a/src/pip/_internal/operations/build/wheel_legacy.py
+++ b/src/pip/_internal/operations/build/wheel_legacy.py
@@ -4,11 +4,7 @@ from typing import List, Optional
from pip._internal.cli.spinners import open_spinner
from pip._internal.utils.setuptools_build import make_setuptools_bdist_wheel_args
-from pip._internal.utils.subprocess import (
- LOG_DIVIDER,
- call_subprocess,
- format_command_args,
-)
+from pip._internal.utils.subprocess import call_subprocess, format_command_args
logger = logging.getLogger(__name__)
@@ -28,7 +24,7 @@ def format_command_result(
else:
if not command_output.endswith("\n"):
command_output += "\n"
- text += f"Command output:\n{command_output}{LOG_DIVIDER}"
+ text += f"Command output:\n{command_output}"
return text
@@ -86,6 +82,7 @@ def build_wheel_legacy(
try:
output = call_subprocess(
wheel_args,
+ command_desc="python setup.py bdist_wheel",
cwd=source_dir,
spinner=spinner,
)
diff --git a/src/pip/_internal/operations/check.py b/src/pip/_internal/operations/check.py
index fb3ac8b9c..e3bce69b2 100644
--- a/src/pip/_internal/operations/check.py
+++ b/src/pip/_internal/operations/check.py
@@ -75,7 +75,7 @@ def check_package_set(
if name not in package_set:
missed = True
if req.marker is not None:
- missed = req.marker.evaluate()
+ missed = req.marker.evaluate({"extra": ""})
if missed:
missing_deps.add((name, req))
continue
diff --git a/src/pip/_internal/operations/freeze.py b/src/pip/_internal/operations/freeze.py
index 456554085..930d4c600 100644
--- a/src/pip/_internal/operations/freeze.py
+++ b/src/pip/_internal/operations/freeze.py
@@ -1,7 +1,7 @@
import collections
import logging
import os
-from typing import Container, Dict, Iterable, Iterator, List, NamedTuple, Optional, Set
+from typing import Container, Dict, Generator, Iterable, List, NamedTuple, Optional, Set
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.packaging.version import Version
@@ -31,7 +31,7 @@ def freeze(
isolated: bool = False,
exclude_editable: bool = False,
skip: Container[str] = (),
-) -> Iterator[str]:
+) -> Generator[str, None, None]:
installations: Dict[str, FrozenRequirement] = {}
dists = get_environment(paths).iter_installed_distributions(
diff --git a/src/pip/_internal/operations/install/editable_legacy.py b/src/pip/_internal/operations/install/editable_legacy.py
index 5bd72ca19..bebe24e6d 100644
--- a/src/pip/_internal/operations/install/editable_legacy.py
+++ b/src/pip/_internal/operations/install/editable_legacy.py
@@ -1,7 +1,7 @@
"""Legacy editable installation process, i.e. `setup.py develop`.
"""
import logging
-from typing import List, Optional, Sequence
+from typing import Optional, Sequence
from pip._internal.build_env import BuildEnvironment
from pip._internal.utils.logging import indent_log
@@ -12,7 +12,7 @@ logger = logging.getLogger(__name__)
def install_editable(
- install_options: List[str],
+ *,
global_options: Sequence[str],
prefix: Optional[str],
home: Optional[str],
@@ -31,7 +31,6 @@ def install_editable(
args = make_setuptools_develop_args(
setup_py_path,
global_options=global_options,
- install_options=install_options,
no_user_config=isolated,
prefix=prefix,
home=home,
@@ -42,5 +41,6 @@ def install_editable(
with build_env:
call_subprocess(
args,
+ command_desc="python setup.py develop",
cwd=unpacked_source_directory,
)
diff --git a/src/pip/_internal/operations/install/legacy.py b/src/pip/_internal/operations/install/legacy.py
index 2206c9309..0b108d0ca 100644
--- a/src/pip/_internal/operations/install/legacy.py
+++ b/src/pip/_internal/operations/install/legacy.py
@@ -3,13 +3,12 @@
import logging
import os
-from distutils.util import change_root
from typing import List, Optional, Sequence
from pip._internal.build_env import BuildEnvironment
-from pip._internal.exceptions import InstallationError
+from pip._internal.exceptions import InstallationError, LegacyInstallFailure
+from pip._internal.locations.base import change_root
from pip._internal.models.scheme import Scheme
-from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import ensure_dir
from pip._internal.utils.setuptools_build import make_setuptools_install_args
from pip._internal.utils.subprocess import runner_with_spinner_message
@@ -18,10 +17,6 @@ from pip._internal.utils.temp_dir import TempDirectory
logger = logging.getLogger(__name__)
-class LegacyInstallFailure(Exception):
- pass
-
-
def write_installed_files_from_setuptools_record(
record_lines: List[str],
root: Optional[str],
@@ -60,7 +55,6 @@ def write_installed_files_from_setuptools_record(
def install(
- install_options: List[str],
global_options: Sequence[str],
root: Optional[str],
home: Optional[str],
@@ -84,7 +78,6 @@ def install(
install_args = make_setuptools_install_args(
setup_py_path,
global_options=global_options,
- install_options=install_options,
record_filename=record_filename,
root=root,
prefix=prefix,
@@ -98,7 +91,7 @@ def install(
runner = runner_with_spinner_message(
f"Running setup.py install for {req_name}"
)
- with indent_log(), build_env:
+ with build_env:
runner(
cmd=install_args,
cwd=unpacked_source_directory,
@@ -111,7 +104,7 @@ def install(
except Exception as e:
# Signal to the caller that we didn't install the new package
- raise LegacyInstallFailure from e
+ raise LegacyInstallFailure(package_details=req_name) from e
# At this point, we have successfully installed the requirement.
diff --git a/src/pip/_internal/operations/install/wheel.py b/src/pip/_internal/operations/install/wheel.py
index e191b1343..c79941398 100644
--- a/src/pip/_internal/operations/install/wheel.py
+++ b/src/pip/_internal/operations/install/wheel.py
@@ -22,6 +22,7 @@ from typing import (
BinaryIO,
Callable,
Dict,
+ Generator,
Iterable,
Iterator,
List,
@@ -223,19 +224,16 @@ def _normalized_outrows(
)
-def _record_to_fs_path(record_path: RecordPath) -> str:
- return record_path
+def _record_to_fs_path(record_path: RecordPath, lib_dir: str) -> str:
+ return os.path.join(lib_dir, record_path)
-def _fs_to_record_path(path: str, relative_to: Optional[str] = None) -> RecordPath:
- if relative_to is not None:
- # On Windows, do not handle relative paths if they belong to different
- # logical disks
- if (
- os.path.splitdrive(path)[0].lower()
- == os.path.splitdrive(relative_to)[0].lower()
- ):
- path = os.path.relpath(path, relative_to)
+def _fs_to_record_path(path: str, lib_dir: str) -> RecordPath:
+ # On Windows, do not handle relative paths if they belong to different
+ # logical disks
+ if os.path.splitdrive(path)[0].lower() == os.path.splitdrive(lib_dir)[0].lower():
+ path = os.path.relpath(path, lib_dir)
+
path = path.replace(os.path.sep, "/")
return cast("RecordPath", path)
@@ -258,7 +256,7 @@ def get_csv_rows_for_installed(
old_record_path = cast("RecordPath", row[0])
new_record_path = installed.pop(old_record_path, old_record_path)
if new_record_path in changed:
- digest, length = rehash(_record_to_fs_path(new_record_path))
+ digest, length = rehash(_record_to_fs_path(new_record_path, lib_dir))
else:
digest = row[1] if len(row) > 1 else ""
length = row[2] if len(row) > 2 else ""
@@ -327,7 +325,7 @@ def get_console_script_specs(console: Dict[str, str]) -> List[str]:
scripts_to_generate.append(f"pip{get_major_minor_version()} = {pip_script}")
# Delete any other versioned pip entry points
- pip_ep = [k for k in console if re.match(r"pip(\d(\.\d)?)?$", k)]
+ pip_ep = [k for k in console if re.match(r"pip(\d+(\.\d+)?)?$", k)]
for k in pip_ep:
del console[k]
easy_install_script = console.pop("easy_install", None)
@@ -342,7 +340,7 @@ def get_console_script_specs(console: Dict[str, str]) -> List[str]:
)
# Delete any other versioned easy_install entry points
easy_install_ep = [
- k for k in console if re.match(r"easy_install(-\d\.\d)?$", k)
+ k for k in console if re.match(r"easy_install(-\d+\.\d+)?$", k)
]
for k in easy_install_ep:
del console[k]
@@ -422,7 +420,9 @@ def _raise_for_invalid_entrypoint(specification: str) -> None:
class PipScriptMaker(ScriptMaker):
- def make(self, specification: str, options: Dict[str, Any] = None) -> List[str]:
+ def make(
+ self, specification: str, options: Optional[Dict[str, Any]] = None
+ ) -> List[str]:
_raise_for_invalid_entrypoint(specification)
return super().make(specification, options)
@@ -474,7 +474,7 @@ def _install_wheel(
newpath = _fs_to_record_path(destfile, lib_dir)
installed[srcfile] = newpath
if modified:
- changed.add(_fs_to_record_path(destfile))
+ changed.add(newpath)
def is_dir_path(path: RecordPath) -> bool:
return path.endswith("/")
@@ -589,7 +589,7 @@ def _install_wheel(
file.save()
record_installed(file.src_record_path, file.dest_path, file.changed)
- def pyc_source_file_paths() -> Iterator[str]:
+ def pyc_source_file_paths() -> Generator[str, None, None]:
# We de-duplicate installation paths, since there can be overlap (e.g.
# file in .data maps to same location as file in wheel root).
# Sorting installation paths makes it easier to reproduce and debug
@@ -656,7 +656,7 @@ def _install_wheel(
generated_file_mode = 0o666 & ~current_umask()
@contextlib.contextmanager
- def _generate_file(path: str, **kwargs: Any) -> Iterator[BinaryIO]:
+ def _generate_file(path: str, **kwargs: Any) -> Generator[BinaryIO, None, None]:
with adjacent_tmp_file(path, **kwargs) as f:
yield f
os.chmod(f.name, generated_file_mode)
@@ -706,7 +706,7 @@ def _install_wheel(
@contextlib.contextmanager
-def req_error_context(req_description: str) -> Iterator[None]:
+def req_error_context(req_description: str) -> Generator[None, None, None]:
try:
yield
except InstallationError as e:
diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py
index 34cf9a51b..343a01bef 100644
--- a/src/pip/_internal/operations/prepare.py
+++ b/src/pip/_internal/operations/prepare.py
@@ -19,12 +19,14 @@ from pip._internal.exceptions import (
HashMismatch,
HashUnpinned,
InstallationError,
+ MetadataInconsistent,
NetworkConnectionError,
PreviousBuildDirError,
VcsHashUnsupported,
)
from pip._internal.index.package_finder import PackageFinder
-from pip._internal.metadata import BaseDistribution
+from pip._internal.metadata import BaseDistribution, get_metadata_distribution
+from pip._internal.models.direct_url import ArchiveInfo
from pip._internal.models.link import Link
from pip._internal.models.wheel import Wheel
from pip._internal.network.download import BatchDownloader, Downloader
@@ -33,12 +35,20 @@ from pip._internal.network.lazy_wheel import (
dist_from_wheel_url,
)
from pip._internal.network.session import PipSession
+from pip._internal.operations.build.build_tracker import BuildTracker
from pip._internal.req.req_install import InstallRequirement
-from pip._internal.req.req_tracker import RequirementTracker
-from pip._internal.utils.filesystem import copy2_fixed
+from pip._internal.utils.direct_url_helpers import (
+ direct_url_for_editable,
+ direct_url_from_link,
+)
from pip._internal.utils.hashes import Hashes, MissingHashes
from pip._internal.utils.logging import indent_log
-from pip._internal.utils.misc import display_path, hide_url, is_installable_dir, rmtree
+from pip._internal.utils.misc import (
+ display_path,
+ hash_file,
+ hide_url,
+ is_installable_dir,
+)
from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.utils.unpacking import unpack_file
from pip._internal.vcs import vcs
@@ -48,21 +58,24 @@ logger = logging.getLogger(__name__)
def _get_prepared_distribution(
req: InstallRequirement,
- req_tracker: RequirementTracker,
+ build_tracker: BuildTracker,
finder: PackageFinder,
build_isolation: bool,
+ check_build_deps: bool,
) -> BaseDistribution:
"""Prepare a distribution for installation."""
abstract_dist = make_distribution_for_install_requirement(req)
- with req_tracker.track(req):
- abstract_dist.prepare_distribution_metadata(finder, build_isolation)
+ with build_tracker.track(req):
+ abstract_dist.prepare_distribution_metadata(
+ finder, build_isolation, check_build_deps
+ )
return abstract_dist.get_metadata_distribution()
-def unpack_vcs_link(link: Link, location: str) -> None:
+def unpack_vcs_link(link: Link, location: str, verbosity: int) -> None:
vcs_backend = vcs.get_backend_for_scheme(link.scheme)
assert vcs_backend is not None
- vcs_backend.unpack(location, url=hide_url(link.url))
+ vcs_backend.unpack(location, url=hide_url(link.url), verbosity=verbosity)
class File:
@@ -98,55 +111,6 @@ def get_http_url(
return File(from_path, content_type)
-def _copy2_ignoring_special_files(src: str, dest: str) -> None:
- """Copying special files is not supported, but as a convenience to users
- we skip errors copying them. This supports tools that may create e.g.
- socket files in the project source directory.
- """
- try:
- copy2_fixed(src, dest)
- except shutil.SpecialFileError as e:
- # SpecialFileError may be raised due to either the source or
- # destination. If the destination was the cause then we would actually
- # care, but since the destination directory is deleted prior to
- # copy we ignore all of them assuming it is caused by the source.
- logger.warning(
- "Ignoring special file error '%s' encountered copying %s to %s.",
- str(e),
- src,
- dest,
- )
-
-
-def _copy_source_tree(source: str, target: str) -> None:
- target_abspath = os.path.abspath(target)
- target_basename = os.path.basename(target_abspath)
- target_dirname = os.path.dirname(target_abspath)
-
- def ignore(d: str, names: List[str]) -> List[str]:
- skipped: List[str] = []
- if d == source:
- # Pulling in those directories can potentially be very slow,
- # exclude the following directories if they appear in the top
- # level dir (and only it).
- # See discussion at https://github.com/pypa/pip/pull/6770
- skipped += [".tox", ".nox"]
- if os.path.abspath(d) == target_dirname:
- # Prevent an infinite recursion if the target is in source.
- # This can happen when TMPDIR is set to ${PWD}/...
- # and we copy PWD to TMPDIR.
- skipped += [target_basename]
- return skipped
-
- shutil.copytree(
- source,
- target,
- ignore=ignore,
- symlinks=True,
- copy_function=_copy2_ignoring_special_files,
- )
-
-
def get_file_url(
link: Link, download_dir: Optional[str] = None, hashes: Optional[Hashes] = None
) -> File:
@@ -175,6 +139,7 @@ def unpack_url(
link: Link,
location: str,
download: Downloader,
+ verbosity: int,
download_dir: Optional[str] = None,
hashes: Optional[Hashes] = None,
) -> Optional[File]:
@@ -187,22 +152,10 @@ def unpack_url(
"""
# non-editable vcs urls
if link.is_vcs:
- unpack_vcs_link(link, location)
+ unpack_vcs_link(link, location, verbosity=verbosity)
return None
- # Once out-of-tree-builds are no longer supported, could potentially
- # replace the below condition with `assert not link.is_existing_dir`
- # - unpack_url does not need to be called for in-tree-builds.
- #
- # As further cleanup, _copy_source_tree and accompanying tests can
- # be removed.
- #
- # TODO when use-deprecated=out-of-tree-build is removed
- if link.is_existing_dir():
- if os.path.isdir(location):
- rmtree(location)
- _copy_source_tree(link.file_path, location)
- return None
+ assert not link.is_existing_dir()
# file urls
if link.is_file:
@@ -260,20 +213,21 @@ class RequirementPreparer:
download_dir: Optional[str],
src_dir: str,
build_isolation: bool,
- req_tracker: RequirementTracker,
+ check_build_deps: bool,
+ build_tracker: BuildTracker,
session: PipSession,
progress_bar: str,
finder: PackageFinder,
require_hashes: bool,
use_user_site: bool,
lazy_wheel: bool,
- in_tree_build: bool,
+ verbosity: int,
) -> None:
super().__init__()
self.src_dir = src_dir
self.build_dir = build_dir
- self.req_tracker = req_tracker
+ self.build_tracker = build_tracker
self._session = session
self._download = Downloader(session, progress_bar)
self._batch_download = BatchDownloader(session, progress_bar)
@@ -286,6 +240,9 @@ class RequirementPreparer:
# Is build isolation allowed?
self.build_isolation = build_isolation
+ # Should check build dependencies?
+ self.check_build_deps = check_build_deps
+
# Should hash-checking be required?
self.require_hashes = require_hashes
@@ -295,8 +252,8 @@ class RequirementPreparer:
# Should wheels be downloaded lazily?
self.use_lazy_wheel = lazy_wheel
- # Should in-tree builds be used for local paths?
- self.in_tree_build = in_tree_build
+ # How verbose should underlying tooling be?
+ self.verbosity = verbosity
# Memoized downloaded files, as mapping of url: path.
self._downloaded: Dict[str, str] = {}
@@ -313,6 +270,16 @@ class RequirementPreparer:
message = "Collecting %s"
information = str(req.req or req)
+ # If we used req.req, inject requirement source if available (this
+ # would already be included if we used req directly)
+ if req.req and req.comes_from:
+ if isinstance(req.comes_from, str):
+ comes_from: Optional[str] = req.comes_from
+ else:
+ comes_from = req.comes_from.from_path()
+ if comes_from:
+ information += f" (from {comes_from})"
+
if (message, information) != self._previous_requirement_header:
self._previous_requirement_header = (message, information)
logger.info(message, information)
@@ -331,7 +298,7 @@ class RequirementPreparer:
# directory.
return
assert req.source_dir is None
- if req.link.is_existing_dir() and self.in_tree_build:
+ if req.link.is_existing_dir():
# build local directories in-tree
req.source_dir = req.link.file_path
return
@@ -390,19 +357,72 @@ class RequirementPreparer:
# showing the user what the hash should be.
return req.hashes(trust_internet=False) or MissingHashes()
+ def _fetch_metadata_only(
+ self,
+ req: InstallRequirement,
+ ) -> Optional[BaseDistribution]:
+ if self.require_hashes:
+ logger.debug(
+ "Metadata-only fetching is not used as hash checking is required",
+ )
+ return None
+ # Try PEP 658 metadata first, then fall back to lazy wheel if unavailable.
+ return self._fetch_metadata_using_link_data_attr(
+ req
+ ) or self._fetch_metadata_using_lazy_wheel(req.link)
+
+ def _fetch_metadata_using_link_data_attr(
+ self,
+ req: InstallRequirement,
+ ) -> Optional[BaseDistribution]:
+ """Fetch metadata from the data-dist-info-metadata attribute, if possible."""
+ # (1) Get the link to the metadata file, if provided by the backend.
+ metadata_link = req.link.metadata_link()
+ if metadata_link is None:
+ return None
+ assert req.req is not None
+ logger.info(
+ "Obtaining dependency information for %s from %s",
+ req.req,
+ metadata_link,
+ )
+ # (2) Download the contents of the METADATA file, separate from the dist itself.
+ metadata_file = get_http_url(
+ metadata_link,
+ self._download,
+ hashes=metadata_link.as_hashes(),
+ )
+ with open(metadata_file.path, "rb") as f:
+ metadata_contents = f.read()
+ # (3) Generate a dist just from those file contents.
+ metadata_dist = get_metadata_distribution(
+ metadata_contents,
+ req.link.filename,
+ req.req.name,
+ )
+ # (4) Ensure the Name: field from the METADATA file matches the name from the
+ # install requirement.
+ #
+ # NB: raw_name will fall back to the name from the install requirement if
+ # the Name: field is not present, but it's noted in the raw_name docstring
+ # that that should NEVER happen anyway.
+ if metadata_dist.raw_name != req.req.name:
+ raise MetadataInconsistent(
+ req, "Name", req.req.name, metadata_dist.raw_name
+ )
+ return metadata_dist
+
def _fetch_metadata_using_lazy_wheel(
self,
link: Link,
) -> Optional[BaseDistribution]:
"""Fetch metadata using lazy wheel, if possible."""
+ # --use-feature=fast-deps must be provided.
if not self.use_lazy_wheel:
return None
- if self.require_hashes:
- logger.debug("Lazy wheel is not used as hash checking is required")
- return None
if link.is_file or not link.is_wheel:
logger.debug(
- "Lazy wheel is not used as %r does not points to a remote wheel",
+ "Lazy wheel is not used as %r does not point to a remote wheel",
link,
)
return None
@@ -458,13 +478,12 @@ class RequirementPreparer:
) -> BaseDistribution:
"""Prepare a requirement to be obtained from req.link."""
assert req.link
- link = req.link
self._log_preparing_link(req)
with indent_log():
# Check if the relevant file is already available
# in the download directory
file_path = None
- if self.download_dir is not None and link.is_wheel:
+ if self.download_dir is not None and req.link.is_wheel:
hashes = self._get_linked_req_hashes(req)
file_path = _check_download_dir(req.link, self.download_dir, hashes)
@@ -473,10 +492,10 @@ class RequirementPreparer:
self._downloaded[req.link.url] = file_path
else:
# The file is not available, attempt to fetch only metadata
- wheel_dist = self._fetch_metadata_using_lazy_wheel(link)
- if wheel_dist is not None:
+ metadata_dist = self._fetch_metadata_only(req)
+ if metadata_dist is not None:
req.needs_more_preparation = True
- return wheel_dist
+ return metadata_dist
# None of the optimizations worked, fully prepare the requirement
return self._prepare_linked_requirement(req, parallel_builds)
@@ -520,12 +539,17 @@ class RequirementPreparer:
self._ensure_link_req_src_dir(req, parallel_builds)
hashes = self._get_linked_req_hashes(req)
- if link.is_existing_dir() and self.in_tree_build:
+ if link.is_existing_dir():
local_file = None
elif link.url not in self._downloaded:
try:
local_file = unpack_url(
- link, req.source_dir, self._download, self.download_dir, hashes
+ link,
+ req.source_dir,
+ self._download,
+ self.verbosity,
+ self.download_dir,
+ hashes,
)
except NetworkConnectionError as exc:
raise InstallationError(
@@ -538,6 +562,23 @@ class RequirementPreparer:
hashes.check_against_path(file_path)
local_file = File(file_path, content_type=None)
+ # If download_info is set, we got it from the wheel cache.
+ if req.download_info is None:
+ # Editables don't go through this function (see
+ # prepare_editable_requirement).
+ assert not req.editable
+ req.download_info = direct_url_from_link(link, req.source_dir)
+ # Make sure we have a hash in download_info. If we got it as part of the
+ # URL, it will have been verified and we can rely on it. Otherwise we
+ # compute it from the downloaded file.
+ if (
+ isinstance(req.download_info.info, ArchiveInfo)
+ and not req.download_info.info.hash
+ and local_file
+ ):
+ hash = hash_file(local_file.path)[0].hexdigest()
+ req.download_info.info.hash = f"sha256={hash}"
+
# For use in later processing,
# preserve the file path on the requirement.
if local_file:
@@ -545,9 +586,10 @@ class RequirementPreparer:
dist = _get_prepared_distribution(
req,
- self.req_tracker,
+ self.build_tracker,
self.finder,
self.build_isolation,
+ self.check_build_deps,
)
return dist
@@ -595,12 +637,15 @@ class RequirementPreparer:
)
req.ensure_has_source_dir(self.src_dir)
req.update_editable()
+ assert req.source_dir
+ req.download_info = direct_url_for_editable(req.unpacked_source_directory)
dist = _get_prepared_distribution(
req,
- self.req_tracker,
+ self.build_tracker,
self.finder,
self.build_isolation,
+ self.check_build_deps,
)
req.check_if_exists(self.use_user_site)
diff --git a/src/pip/_internal/pyproject.py b/src/pip/_internal/pyproject.py
index 31534a3a9..1de9f0fde 100644
--- a/src/pip/_internal/pyproject.py
+++ b/src/pip/_internal/pyproject.py
@@ -1,3 +1,4 @@
+import importlib.util
import os
from collections import namedtuple
from typing import Any, List, Optional
@@ -5,7 +6,11 @@ from typing import Any, List, Optional
from pip._vendor import tomli
from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
-from pip._internal.exceptions import InstallationError
+from pip._internal.exceptions import (
+ InstallationError,
+ InvalidPyProjectBuildRequires,
+ MissingPyProjectBuildRequires,
+)
def _is_list_of_str(obj: Any) -> bool:
@@ -56,7 +61,7 @@ def load_pyproject_toml(
if has_pyproject:
with open(pyproject_toml, encoding="utf-8") as f:
- pp_toml = tomli.load(f)
+ pp_toml = tomli.loads(f.read())
build_system = pp_toml.get("build-system")
else:
build_system = None
@@ -85,9 +90,15 @@ def load_pyproject_toml(
# If we haven't worked out whether to use PEP 517 yet,
# and the user hasn't explicitly stated a preference,
- # we do so if the project has a pyproject.toml file.
+ # we do so if the project has a pyproject.toml file
+ # or if we cannot import setuptools.
+
+ # We fallback to PEP 517 when without setuptools,
+ # so setuptools can be installed as a default build backend.
+ # For more info see:
+ # https://discuss.python.org/t/pip-without-setuptools-could-the-experience-be-improved/11810/9
elif use_pep517 is None:
- use_pep517 = has_pyproject
+ use_pep517 = has_pyproject or not importlib.util.find_spec("setuptools")
# At this point, we know whether we're going to use PEP 517.
assert use_pep517 is not None
@@ -119,47 +130,28 @@ def load_pyproject_toml(
# Ensure that the build-system section in pyproject.toml conforms
# to PEP 518.
- error_template = (
- "{package} has a pyproject.toml file that does not comply "
- "with PEP 518: {reason}"
- )
# Specifying the build-system table but not the requires key is invalid
if "requires" not in build_system:
- raise InstallationError(
- error_template.format(
- package=req_name,
- reason=(
- "it has a 'build-system' table but not "
- "'build-system.requires' which is mandatory in the table"
- ),
- )
- )
+ raise MissingPyProjectBuildRequires(package=req_name)
# Error out if requires is not a list of strings
requires = build_system["requires"]
if not _is_list_of_str(requires):
- raise InstallationError(
- error_template.format(
- package=req_name,
- reason="'build-system.requires' is not a list of strings.",
- )
+ raise InvalidPyProjectBuildRequires(
+ package=req_name,
+ reason="It is not a list of strings.",
)
# Each requirement must be valid as per PEP 508
for requirement in requires:
try:
Requirement(requirement)
- except InvalidRequirement:
- raise InstallationError(
- error_template.format(
- package=req_name,
- reason=(
- "'build-system.requires' contains an invalid "
- "requirement: {!r}".format(requirement)
- ),
- )
- )
+ except InvalidRequirement as error:
+ raise InvalidPyProjectBuildRequires(
+ package=req_name,
+ reason=f"It contains an invalid requirement: {requirement!r}",
+ ) from error
backend = build_system.get("build-backend")
backend_path = build_system.get("backend-path", [])
@@ -167,9 +159,8 @@ def load_pyproject_toml(
if backend is None:
# If the user didn't specify a backend, we assume they want to use
# the setuptools backend. But we can't be sure they have included
- # a version of setuptools which supplies the backend, or wheel
- # (which is needed by the backend) in their requirements. So we
- # make a note to check that those requirements are present once
+ # a version of setuptools which supplies the backend. So we
+ # make a note to check that this requirement is present once
# we have set up the environment.
# This is quite a lot of work to check for a very specific case. But
# the problem is, that case is potentially quite common - projects that
@@ -178,6 +169,6 @@ def load_pyproject_toml(
# tools themselves. The original PEP 518 code had a similar check (but
# implemented in a different way).
backend = "setuptools.build_meta:__legacy__"
- check = ["setuptools>=40.8.0", "wheel"]
+ check = ["setuptools>=40.8.0"]
return BuildSystemDetails(requires, backend, check, backend_path)
diff --git a/src/pip/_internal/req/__init__.py b/src/pip/_internal/req/__init__.py
index 70dea27a6..16de903a4 100644
--- a/src/pip/_internal/req/__init__.py
+++ b/src/pip/_internal/req/__init__.py
@@ -1,6 +1,6 @@
import collections
import logging
-from typing import Iterator, List, Optional, Sequence, Tuple
+from typing import Generator, List, Optional, Sequence, Tuple
from pip._internal.utils.logging import indent_log
@@ -28,7 +28,7 @@ class InstallationResult:
def _validate_requirements(
requirements: List[InstallRequirement],
-) -> Iterator[Tuple[str, InstallRequirement]]:
+) -> Generator[Tuple[str, InstallRequirement], None, None]:
for req in requirements:
assert req.name, f"invalid to-be-installed requirement: {req}"
yield req.name, req
@@ -36,7 +36,6 @@ def _validate_requirements(
def install_given_reqs(
requirements: List[InstallRequirement],
- install_options: List[str],
global_options: Sequence[str],
root: Optional[str],
home: Optional[str],
@@ -71,7 +70,6 @@ def install_given_reqs(
try:
requirement.install(
- install_options,
global_options,
root=root,
home=home,
diff --git a/src/pip/_internal/req/constructors.py b/src/pip/_internal/req/constructors.py
index 4a594037f..854b1b058 100644
--- a/src/pip/_internal/req/constructors.py
+++ b/src/pip/_internal/req/constructors.py
@@ -16,7 +16,6 @@ from typing import Any, Dict, Optional, Set, Tuple, Union
from pip._vendor.packaging.markers import Marker
from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
from pip._vendor.packaging.specifiers import Specifier
-from pip._vendor.pkg_resources import RequirementParseError, parse_requirements
from pip._internal.exceptions import InstallationError
from pip._internal.models.index import PyPI, TestPyPI
@@ -113,31 +112,56 @@ def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
return package_name, url, set()
+def check_first_requirement_in_file(filename: str) -> None:
+ """Check if file is parsable as a requirements file.
+
+ This is heavily based on ``pkg_resources.parse_requirements``, but
+ simplified to just check the first meaningful line.
+
+ :raises InvalidRequirement: If the first meaningful line cannot be parsed
+ as an requirement.
+ """
+ with open(filename, encoding="utf-8", errors="ignore") as f:
+ # Create a steppable iterator, so we can handle \-continuations.
+ lines = (
+ line
+ for line in (line.strip() for line in f)
+ if line and not line.startswith("#") # Skip blank lines/comments.
+ )
+
+ for line in lines:
+ # Drop comments -- a hash without a space may be in a URL.
+ if " #" in line:
+ line = line[: line.find(" #")]
+ # If there is a line continuation, drop it, and append the next line.
+ if line.endswith("\\"):
+ line = line[:-2].strip() + next(lines, "")
+ Requirement(line)
+ return
+
+
def deduce_helpful_msg(req: str) -> str:
"""Returns helpful msg in case requirements file does not exist,
or cannot be parsed.
:params req: Requirements file path
"""
- msg = ""
- if os.path.exists(req):
- msg = " The path does exist. "
- # Try to parse and check if it is a requirements file.
- try:
- with open(req) as fp:
- # parse first line only
- next(parse_requirements(fp.read()))
- msg += (
- "The argument you provided "
- "({}) appears to be a"
- " requirements file. If that is the"
- " case, use the '-r' flag to install"
- " the packages specified within it."
- ).format(req)
- except RequirementParseError:
- logger.debug("Cannot parse '%s' as requirements file", req, exc_info=True)
+ if not os.path.exists(req):
+ return f" File '{req}' does not exist."
+ msg = " The path does exist. "
+ # Try to parse and check if it is a requirements file.
+ try:
+ check_first_requirement_in_file(req)
+ except InvalidRequirement:
+ logger.debug("Cannot parse '%s' as requirements file", req)
else:
- msg += f" File '{req}' does not exist."
+ msg += (
+ f"The argument you provided "
+ f"({req}) appears to be a"
+ f" requirements file. If that is the"
+ f" case, use the '-r' flag to install"
+ f" the packages specified within it."
+ )
return msg
@@ -183,6 +207,7 @@ def install_req_from_editable(
constraint: bool = False,
user_supplied: bool = False,
permit_editable_wheels: bool = False,
+ config_settings: Optional[Dict[str, str]] = None,
) -> InstallRequirement:
parts = parse_req_from_editable(editable_req)
@@ -197,9 +222,9 @@ def install_req_from_editable(
constraint=constraint,
use_pep517=use_pep517,
isolated=isolated,
- install_options=options.get("install_options", []) if options else [],
global_options=options.get("global_options", []) if options else [],
hash_options=options.get("hashes", {}) if options else {},
+ config_settings=config_settings,
extras=parts.extras,
)
@@ -356,6 +381,7 @@ def install_req_from_line(
constraint: bool = False,
line_source: Optional[str] = None,
user_supplied: bool = False,
+ config_settings: Optional[Dict[str, str]] = None,
) -> InstallRequirement:
"""Creates an InstallRequirement from a name, which might be a
requirement, directory containing 'setup.py', filename, or URL.
@@ -372,9 +398,9 @@ def install_req_from_line(
markers=parts.markers,
use_pep517=use_pep517,
isolated=isolated,
- install_options=options.get("install_options", []) if options else [],
global_options=options.get("global_options", []) if options else [],
hash_options=options.get("hashes", {}) if options else {},
+ config_settings=config_settings,
constraint=constraint,
extras=parts.extras,
user_supplied=user_supplied,
@@ -387,6 +413,7 @@ def install_req_from_req_string(
isolated: bool = False,
use_pep517: Optional[bool] = None,
user_supplied: bool = False,
+ config_settings: Optional[Dict[str, str]] = None,
) -> InstallRequirement:
try:
req = get_requirement(req_string)
@@ -416,6 +443,7 @@ def install_req_from_req_string(
isolated=isolated,
use_pep517=use_pep517,
user_supplied=user_supplied,
+ config_settings=config_settings,
)
@@ -424,6 +452,7 @@ def install_req_from_parsed_requirement(
isolated: bool = False,
use_pep517: Optional[bool] = None,
user_supplied: bool = False,
+ config_settings: Optional[Dict[str, str]] = None,
) -> InstallRequirement:
if parsed_req.is_editable:
req = install_req_from_editable(
@@ -433,6 +462,7 @@ def install_req_from_parsed_requirement(
constraint=parsed_req.constraint,
isolated=isolated,
user_supplied=user_supplied,
+ config_settings=config_settings,
)
else:
@@ -445,6 +475,7 @@ def install_req_from_parsed_requirement(
constraint=parsed_req.constraint,
line_source=parsed_req.line_source,
user_supplied=user_supplied,
+ config_settings=config_settings,
)
return req
@@ -460,7 +491,8 @@ def install_req_from_link_and_ireq(
markers=ireq.markers,
use_pep517=ireq.use_pep517,
isolated=ireq.isolated,
- install_options=ireq.install_options,
global_options=ireq.global_options,
hash_options=ireq.hash_options,
+ config_settings=ireq.config_settings,
+ user_supplied=ireq.user_supplied,
)
diff --git a/src/pip/_internal/req/req_file.py b/src/pip/_internal/req/req_file.py
index 03ae50492..f8f07b0cd 100644
--- a/src/pip/_internal/req/req_file.py
+++ b/src/pip/_internal/req/req_file.py
@@ -13,8 +13,8 @@ from typing import (
Any,
Callable,
Dict,
+ Generator,
Iterable,
- Iterator,
List,
Optional,
Tuple,
@@ -69,7 +69,6 @@ SUPPORTED_OPTIONS: List[Callable[..., optparse.Option]] = [
# options to be passed to requirements
SUPPORTED_OPTIONS_REQ: List[Callable[..., optparse.Option]] = [
- cmdoptions.install_options,
cmdoptions.global_options,
cmdoptions.hash,
]
@@ -129,7 +128,7 @@ def parse_requirements(
finder: Optional["PackageFinder"] = None,
options: Optional[optparse.Values] = None,
constraint: bool = False,
-) -> Iterator[ParsedRequirement]:
+) -> Generator[ParsedRequirement, None, None]:
"""Parse a requirements file and yield ParsedRequirement instances.
:param filename: Path or url of requirements file.
@@ -186,10 +185,6 @@ def handle_requirement_line(
constraint=line.constraint,
)
else:
- if options:
- # Disable wheels if the user has specified build options
- cmdoptions.check_install_build_global(options, line.opts)
-
# get the options that apply to requirements
req_options = {}
for dest in SUPPORTED_OPTIONS_REQ_DEST:
@@ -229,11 +224,13 @@ def handle_option_line(
if finder:
find_links = finder.find_links
index_urls = finder.index_urls
- if opts.index_url:
- index_urls = [opts.index_url]
+ no_index = finder.search_scope.no_index
if opts.no_index is True:
+ no_index = True
index_urls = []
- if opts.extra_index_urls:
+ if opts.index_url and not no_index:
+ index_urls = [opts.index_url]
+ if opts.extra_index_urls and not no_index:
index_urls.extend(opts.extra_index_urls)
if opts.find_links:
# FIXME: it would be nice to keep track of the source
@@ -253,6 +250,7 @@ def handle_option_line(
search_scope = SearchScope(
find_links=find_links,
index_urls=index_urls,
+ no_index=no_index,
)
finder.search_scope = search_scope
@@ -321,13 +319,15 @@ class RequirementsFileParser:
self._session = session
self._line_parser = line_parser
- def parse(self, filename: str, constraint: bool) -> Iterator[ParsedLine]:
+ def parse(
+ self, filename: str, constraint: bool
+ ) -> Generator[ParsedLine, None, None]:
"""Parse a given file, yielding parsed lines."""
yield from self._parse_and_recurse(filename, constraint)
def _parse_and_recurse(
self, filename: str, constraint: bool
- ) -> Iterator[ParsedLine]:
+ ) -> Generator[ParsedLine, None, None]:
for line in self._parse_file(filename, constraint):
if not line.is_requirement and (
line.opts.requirements or line.opts.constraints
@@ -356,7 +356,9 @@ class RequirementsFileParser:
else:
yield line
- def _parse_file(self, filename: str, constraint: bool) -> Iterator[ParsedLine]:
+ def _parse_file(
+ self, filename: str, constraint: bool
+ ) -> Generator[ParsedLine, None, None]:
_, content = get_file_content(filename, self._session)
lines_enum = preprocess(content)
@@ -390,7 +392,12 @@ def get_line_parser(finder: Optional["PackageFinder"]) -> LineParser:
args_str, options_str = break_args_options(line)
- opts, _ = parser.parse_args(shlex.split(options_str), defaults)
+ try:
+ options = shlex.split(options_str)
+ except ValueError as e:
+ raise OptionParsingError(f"Could not split options: {options_str}") from e
+
+ opts, _ = parser.parse_args(options, defaults)
return args_str, opts
diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py
index 19f1904af..9807f690f 100644
--- a/src/pip/_internal/req/req_install.py
+++ b/src/pip/_internal/req/req_install.py
@@ -8,21 +8,28 @@ import shutil
import sys
import uuid
import zipfile
+from optparse import Values
from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Union
-from pip._vendor import pkg_resources
from pip._vendor.packaging.markers import Marker
from pip._vendor.packaging.requirements import Requirement
from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.packaging.version import Version
from pip._vendor.packaging.version import parse as parse_version
-from pip._vendor.pep517.wrappers import Pep517HookCaller
-from pip._vendor.pkg_resources import Distribution
+from pip._vendor.pyproject_hooks import BuildBackendHookCaller
from pip._internal.build_env import BuildEnvironment, NoOpBuildEnvironment
-from pip._internal.exceptions import InstallationError
+from pip._internal.exceptions import InstallationError, LegacyInstallFailure
from pip._internal.locations import get_scheme
+from pip._internal.metadata import (
+ BaseDistribution,
+ get_default_environment,
+ get_directory_distribution,
+ get_wheel_distribution,
+)
+from pip._internal.metadata.base import FilesystemWheel
+from pip._internal.models.direct_url import DirectUrl
from pip._internal.models.link import Link
from pip._internal.operations.build.metadata import generate_metadata
from pip._internal.operations.build.metadata_editable import generate_editable_metadata
@@ -32,28 +39,25 @@ from pip._internal.operations.build.metadata_legacy import (
from pip._internal.operations.install.editable_legacy import (
install_editable as install_editable_legacy,
)
-from pip._internal.operations.install.legacy import LegacyInstallFailure
from pip._internal.operations.install.legacy import install as install_legacy
from pip._internal.operations.install.wheel import install_wheel
from pip._internal.pyproject import load_pyproject_toml, make_pyproject_path
from pip._internal.req.req_uninstall import UninstallPathSet
-from pip._internal.utils.deprecation import deprecated
+from pip._internal.utils.deprecation import LegacyInstallReason, deprecated
from pip._internal.utils.direct_url_helpers import (
direct_url_for_editable,
direct_url_from_link,
)
from pip._internal.utils.hashes import Hashes
from pip._internal.utils.misc import (
+ ConfiguredBuildBackendHookCaller,
ask_path_exists,
backup_dir,
display_path,
- dist_in_site_packages,
- dist_in_usersite,
- get_distribution,
hide_url,
redact_auth_from_url,
)
-from pip._internal.utils.packaging import get_metadata
+from pip._internal.utils.packaging import safe_extra
from pip._internal.utils.subprocess import runner_with_spinner_message
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
from pip._internal.utils.virtualenv import running_under_virtualenv
@@ -62,32 +66,6 @@ from pip._internal.vcs import vcs
logger = logging.getLogger(__name__)
-def _get_dist(metadata_directory: str) -> Distribution:
- """Return a pkg_resources.Distribution for the provided
- metadata directory.
- """
- dist_dir = metadata_directory.rstrip(os.sep)
-
- # Build a PathMetadata object, from path to metadata. :wink:
- base_dir, dist_dir_name = os.path.split(dist_dir)
- metadata = pkg_resources.PathMetadata(base_dir, dist_dir)
-
- # Determine the correct Distribution object type.
- if dist_dir.endswith(".egg-info"):
- dist_cls = pkg_resources.Distribution
- dist_name = os.path.splitext(dist_dir_name)[0]
- else:
- assert dist_dir.endswith(".dist-info")
- dist_cls = pkg_resources.DistInfoDistribution
- dist_name = os.path.splitext(dist_dir_name)[0].split("-")[0]
-
- return dist_cls(
- base_dir,
- project_name=dist_name,
- metadata=metadata,
- )
-
-
class InstallRequirement:
"""
Represents something that may be installed later on, may have information
@@ -104,9 +82,10 @@ class InstallRequirement:
markers: Optional[Marker] = None,
use_pep517: Optional[bool] = None,
isolated: bool = False,
- install_options: Optional[List[str]] = None,
+ *,
global_options: Optional[List[str]] = None,
hash_options: Optional[Dict[str, List[str]]] = None,
+ config_settings: Optional[Dict[str, str]] = None,
constraint: bool = False,
extras: Collection[str] = (),
user_supplied: bool = False,
@@ -118,7 +97,7 @@ class InstallRequirement:
self.constraint = constraint
self.editable = editable
self.permit_editable_wheels = permit_editable_wheels
- self.legacy_install_reason: Optional[int] = None
+ self.legacy_install_reason: Optional[LegacyInstallReason] = None
# source_dir is the local directory where the linked requirement is
# located, or unpacked. In case unpacking is needed, creating and
@@ -137,6 +116,10 @@ class InstallRequirement:
self.link = self.original_link = link
self.original_link_is_in_wheel_cache = False
+ # Information about the location of the artifact that was downloaded . This
+ # property is guaranteed to be set in resolver results.
+ self.download_info: Optional[DirectUrl] = None
+
# Path to any downloaded or already-existing package.
self.local_file_path: Optional[str] = None
if self.link and self.link.is_file:
@@ -145,16 +128,15 @@ class InstallRequirement:
if extras:
self.extras = extras
elif req:
- self.extras = {pkg_resources.safe_extra(extra) for extra in req.extras}
+ self.extras = {safe_extra(extra) for extra in req.extras}
else:
self.extras = set()
if markers is None and req:
markers = req.marker
self.markers = markers
- # This holds the pkg_resources.Distribution object if this requirement
- # is already available:
- self.satisfied_by: Optional[Distribution] = None
+ # This holds the Distribution object if this requirement is already installed.
+ self.satisfied_by: Optional[BaseDistribution] = None
# Whether the installation process should try to uninstall an existing
# distribution before installing this requirement.
self.should_reinstall = False
@@ -163,9 +145,9 @@ class InstallRequirement:
# Set to True after successful installation
self.install_succeeded: Optional[bool] = None
# Supplied options
- self.install_options = install_options if install_options else []
self.global_options = global_options if global_options else []
self.hash_options = hash_options if hash_options else {}
+ self.config_settings = config_settings
# Set to True after successful preparation of this requirement
self.prepared = False
# User supplied requirement are explicitly requested for installation
@@ -189,7 +171,7 @@ class InstallRequirement:
self.requirements_to_check: List[str] = []
# The PEP 517 backend we should use to build the project
- self.pep517_backend: Optional[Pep517HookCaller] = None
+ self.pep517_backend: Optional[BuildBackendHookCaller] = None
# Are we using PEP 517 for this requirement?
# After pyproject.toml has been loaded, the only valid values are True
@@ -211,7 +193,11 @@ class InstallRequirement:
else:
s = "<InstallRequirement>"
if self.satisfied_by is not None:
- s += " in {}".format(display_path(self.satisfied_by.location))
+ if self.satisfied_by.location is not None:
+ location = display_path(self.satisfied_by.location)
+ else:
+ location = "<memory>"
+ s += f" in {location}"
if self.comes_from:
if isinstance(self.comes_from, str):
comes_from: Optional[str] = self.comes_from
@@ -242,7 +228,7 @@ class InstallRequirement:
def name(self) -> Optional[str]:
if self.req is None:
return None
- return pkg_resources.safe_name(self.req.name)
+ return self.req.name
@functools.lru_cache() # use cached_property in python 3.8+
def supports_pyproject_editable(self) -> bool:
@@ -415,32 +401,24 @@ class InstallRequirement:
"""
if self.req is None:
return
- existing_dist = get_distribution(self.req.name)
+ existing_dist = get_default_environment().get_distribution(self.req.name)
if not existing_dist:
return
- # pkg_resources may contain a different copy of packaging.version from
- # pip in if the downstream distributor does a poor job debundling pip.
- # We avoid existing_dist.parsed_version and let SpecifierSet.contains
- # parses the version instead.
- existing_version = existing_dist.version
- version_compatible = (
- existing_version is not None
- and self.req.specifier.contains(existing_version, prereleases=True)
+ version_compatible = self.req.specifier.contains(
+ existing_dist.version,
+ prereleases=True,
)
if not version_compatible:
self.satisfied_by = None
if use_user_site:
- if dist_in_usersite(existing_dist):
+ if existing_dist.in_usersite:
self.should_reinstall = True
- elif running_under_virtualenv() and dist_in_site_packages(
- existing_dist
- ):
+ elif running_under_virtualenv() and existing_dist.in_site_packages:
raise InstallationError(
- "Will not install to the user site because it will "
- "lack sys.path precedence to {} in {}".format(
- existing_dist.project_name, existing_dist.location
- )
+ f"Will not install to the user site because it will "
+ f"lack sys.path precedence to {existing_dist.raw_name} "
+ f"in {existing_dist.location}"
)
else:
self.should_reinstall = True
@@ -506,7 +484,8 @@ class InstallRequirement:
requires, backend, check, backend_path = pyproject_toml_data
self.requirements_to_check = check
self.pyproject_requires = requires
- self.pep517_backend = Pep517HookCaller(
+ self.pep517_backend = ConfiguredBuildBackendHookCaller(
+ self,
self.unpacked_source_directory,
backend,
backend_path=backend_path,
@@ -540,6 +519,7 @@ class InstallRequirement:
Under legacy processing, call setup.py egg-info.
"""
assert self.source_dir
+ details = self.name or f"from {self.link}"
if self.use_pep517:
assert self.pep517_backend is not None
@@ -551,11 +531,13 @@ class InstallRequirement:
self.metadata_directory = generate_editable_metadata(
build_env=self.build_env,
backend=self.pep517_backend,
+ details=details,
)
else:
self.metadata_directory = generate_metadata(
build_env=self.build_env,
backend=self.pep517_backend,
+ details=details,
)
else:
self.metadata_directory = generate_metadata_legacy(
@@ -563,7 +545,7 @@ class InstallRequirement:
setup_py_path=self.setup_py_path,
source_dir=self.unpacked_source_directory,
isolated=self.isolated,
- details=self.name or f"from {self.link}",
+ details=details,
)
# Act on the newly generated metadata, based on the name and version.
@@ -577,12 +559,21 @@ class InstallRequirement:
@property
def metadata(self) -> Any:
if not hasattr(self, "_metadata"):
- self._metadata = get_metadata(self.get_dist())
+ self._metadata = self.get_dist().metadata
return self._metadata
- def get_dist(self) -> Distribution:
- return _get_dist(self.metadata_directory)
+ def get_dist(self) -> BaseDistribution:
+ if self.metadata_directory:
+ return get_directory_distribution(self.metadata_directory)
+ elif self.local_file_path and self.is_wheel:
+ return get_wheel_distribution(
+ FilesystemWheel(self.local_file_path), canonicalize_name(self.name)
+ )
+ raise AssertionError(
+ f"InstallRequirement {self} has no metadata directory and no wheel: "
+ f"can't make a distribution."
+ )
def assert_source_matches_version(self) -> None:
assert self.source_dir
@@ -642,7 +633,7 @@ class InstallRequirement:
# So here, if it's neither a path nor a valid VCS URL, it's a bug.
assert vcs_backend, f"Unsupported VCS URL {self.link.url}"
hidden_url = hide_url(self.link.url)
- vcs_backend.obtain(self.source_dir, url=hidden_url)
+ vcs_backend.obtain(self.source_dir, url=hidden_url, verbosity=0)
# Top-level Actions
def uninstall(
@@ -661,7 +652,7 @@ class InstallRequirement:
"""
assert self.req
- dist = get_distribution(self.req.name)
+ dist = get_default_environment().get_distribution(self.req.name)
if not dist:
logger.warning("Skipping %s as it is not installed.", self.name)
return None
@@ -753,7 +744,6 @@ class InstallRequirement:
def install(
self,
- install_options: List[str],
global_options: Optional[Sequence[str]] = None,
root: Optional[str] = None,
home: Optional[str] = None,
@@ -774,8 +764,7 @@ class InstallRequirement:
global_options = global_options if global_options is not None else []
if self.editable and not self.is_wheel:
install_editable_legacy(
- install_options,
- global_options,
+ global_options=global_options,
prefix=prefix,
home=home,
use_user_site=use_user_site,
@@ -791,6 +780,7 @@ class InstallRequirement:
if self.is_wheel:
assert self.local_file_path
direct_url = None
+ # TODO this can be refactored to direct_url = self.download_info
if self.editable:
direct_url = direct_url_for_editable(self.unpacked_source_directory)
elif self.original_link:
@@ -814,17 +804,20 @@ class InstallRequirement:
# TODO: Why don't we do this for editable installs?
- # Extend the list of global and install options passed on to
+ # Extend the list of global options passed on to
# the setup.py call with the ones from the requirements file.
# Options specified in requirements file override those
# specified on the command line, since the last option given
# to setup.py is the one that is used.
global_options = list(global_options) + self.global_options
- install_options = list(install_options) + self.install_options
try:
+ if (
+ self.legacy_install_reason is not None
+ and self.legacy_install_reason.emit_before_install
+ ):
+ self.legacy_install_reason.emit_deprecation(self.name)
success = install_legacy(
- install_options=install_options,
global_options=global_options,
root=root,
home=home,
@@ -841,25 +834,19 @@ class InstallRequirement:
)
except LegacyInstallFailure as exc:
self.install_succeeded = False
- raise exc.__cause__
+ raise exc
except Exception:
self.install_succeeded = True
raise
self.install_succeeded = success
- if success and self.legacy_install_reason == 8368:
- deprecated(
- reason=(
- "{} was installed using the legacy 'setup.py install' "
- "method, because a wheel could not be built for it.".format(
- self.name
- )
- ),
- replacement="to fix the wheel build issue reported above",
- gone_in=None,
- issue=8368,
- )
+ if (
+ success
+ and self.legacy_install_reason is not None
+ and self.legacy_install_reason.emit_after_success
+ ):
+ self.legacy_install_reason.emit_deprecation(self.name)
def check_invalid_constraint_type(req: InstallRequirement) -> str:
@@ -889,3 +876,27 @@ def check_invalid_constraint_type(req: InstallRequirement) -> str:
)
return problem
+
+
+def _has_option(options: Values, reqs: List[InstallRequirement], option: str) -> bool:
+ if getattr(options, option, None):
+ return True
+ for req in reqs:
+ if getattr(req, option, None):
+ return True
+ return False
+
+
+def check_legacy_setup_py_options(
+ options: Values,
+ reqs: List[InstallRequirement],
+) -> None:
+ has_build_options = _has_option(options, reqs, "build_options")
+ has_global_options = _has_option(options, reqs, "global_options")
+ if has_build_options or has_global_options:
+ logger.warning(
+ "Implying --no-binary=:all: due to the presence of "
+ "--build-option / --global-option. "
+ "Consider using --config-settings for more flexibility.",
+ )
+ options.format_control.disallow_binaries()
diff --git a/src/pip/_internal/req/req_set.py b/src/pip/_internal/req/req_set.py
index 6626c37e2..ec7a6e07a 100644
--- a/src/pip/_internal/req/req_set.py
+++ b/src/pip/_internal/req/req_set.py
@@ -1,13 +1,10 @@
import logging
from collections import OrderedDict
-from typing import Dict, Iterable, List, Optional, Tuple
+from typing import Dict, List
from pip._vendor.packaging.utils import canonicalize_name
-from pip._internal.exceptions import InstallationError
-from pip._internal.models.wheel import Wheel
from pip._internal.req.req_install import InstallRequirement
-from pip._internal.utils import compatibility_tags
logger = logging.getLogger(__name__)
@@ -51,123 +48,6 @@ class RequirementSet:
project_name = canonicalize_name(install_req.name)
self.requirements[project_name] = install_req
- def add_requirement(
- self,
- install_req: InstallRequirement,
- parent_req_name: Optional[str] = None,
- extras_requested: Optional[Iterable[str]] = None,
- ) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]:
- """Add install_req as a requirement to install.
-
- :param parent_req_name: The name of the requirement that needed this
- added. The name is used because when multiple unnamed requirements
- resolve to the same name, we could otherwise end up with dependency
- links that point outside the Requirements set. parent_req must
- already be added. Note that None implies that this is a user
- supplied requirement, vs an inferred one.
- :param extras_requested: an iterable of extras used to evaluate the
- environment markers.
- :return: Additional requirements to scan. That is either [] if
- the requirement is not applicable, or [install_req] if the
- requirement is applicable and has just been added.
- """
- # If the markers do not match, ignore this requirement.
- if not install_req.match_markers(extras_requested):
- logger.info(
- "Ignoring %s: markers '%s' don't match your environment",
- install_req.name,
- install_req.markers,
- )
- return [], None
-
- # If the wheel is not supported, raise an error.
- # Should check this after filtering out based on environment markers to
- # allow specifying different wheels based on the environment/OS, in a
- # single requirements file.
- if install_req.link and install_req.link.is_wheel:
- wheel = Wheel(install_req.link.filename)
- tags = compatibility_tags.get_supported()
- if self.check_supported_wheels and not wheel.supported(tags):
- raise InstallationError(
- "{} is not a supported wheel on this platform.".format(
- wheel.filename
- )
- )
-
- # This next bit is really a sanity check.
- assert (
- not install_req.user_supplied or parent_req_name is None
- ), "a user supplied req shouldn't have a parent"
-
- # Unnamed requirements are scanned again and the requirement won't be
- # added as a dependency until after scanning.
- if not install_req.name:
- self.add_unnamed_requirement(install_req)
- return [install_req], None
-
- try:
- existing_req: Optional[InstallRequirement] = self.get_requirement(
- install_req.name
- )
- except KeyError:
- existing_req = None
-
- has_conflicting_requirement = (
- parent_req_name is None
- and existing_req
- and not existing_req.constraint
- and existing_req.extras == install_req.extras
- and existing_req.req
- and install_req.req
- and existing_req.req.specifier != install_req.req.specifier
- )
- if has_conflicting_requirement:
- raise InstallationError(
- "Double requirement given: {} (already in {}, name={!r})".format(
- install_req, existing_req, install_req.name
- )
- )
-
- # When no existing requirement exists, add the requirement as a
- # dependency and it will be scanned again after.
- if not existing_req:
- self.add_named_requirement(install_req)
- # We'd want to rescan this requirement later
- return [install_req], install_req
-
- # Assume there's no need to scan, and that we've already
- # encountered this for scanning.
- if install_req.constraint or not existing_req.constraint:
- return [], existing_req
-
- does_not_satisfy_constraint = install_req.link and not (
- existing_req.link and install_req.link.path == existing_req.link.path
- )
- if does_not_satisfy_constraint:
- raise InstallationError(
- "Could not satisfy constraints for '{}': "
- "installation from path or url cannot be "
- "constrained to a version".format(install_req.name)
- )
- # If we're now installing a constraint, mark the existing
- # object for real installation.
- existing_req.constraint = False
- # If we're now installing a user supplied requirement,
- # mark the existing object as such.
- if install_req.user_supplied:
- existing_req.user_supplied = True
- existing_req.extras = tuple(
- sorted(set(existing_req.extras) | set(install_req.extras))
- )
- logger.debug(
- "Setting %s extras to: %s",
- existing_req,
- existing_req.extras,
- )
- # Return the existing requirement for addition to the parent and
- # scanning again.
- return [existing_req], existing_req
-
def has_requirement(self, name: str) -> bool:
project_name = canonicalize_name(name)
@@ -187,3 +67,16 @@ class RequirementSet:
@property
def all_requirements(self) -> List[InstallRequirement]:
return self.unnamed_requirements + list(self.requirements.values())
+
+ @property
+ def requirements_to_install(self) -> List[InstallRequirement]:
+ """Return the list of requirements that need to be installed.
+
+ TODO remove this property together with the legacy resolver, since the new
+ resolver only returns requirements that need to be installed.
+ """
+ return [
+ install_req
+ for install_req in self.all_requirements
+ if not install_req.constraint and not install_req.satisfied_by
+ ]
diff --git a/src/pip/_internal/req/req_uninstall.py b/src/pip/_internal/req/req_uninstall.py
index b13501790..641aaad76 100644
--- a/src/pip/_internal/req/req_uninstall.py
+++ b/src/pip/_internal/req/req_uninstall.py
@@ -1,57 +1,46 @@
-import csv
import functools
import os
import sys
import sysconfig
from importlib.util import cache_from_source
-from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple
-
-from pip._vendor import pkg_resources
-from pip._vendor.pkg_resources import Distribution
+from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Set, Tuple
from pip._internal.exceptions import UninstallationError
from pip._internal.locations import get_bin_prefix, get_bin_user
+from pip._internal.metadata import BaseDistribution
from pip._internal.utils.compat import WINDOWS
from pip._internal.utils.egg_link import egg_link_path_from_location
from pip._internal.utils.logging import getLogger, indent_log
-from pip._internal.utils.misc import (
- ask,
- dist_in_usersite,
- dist_is_local,
- is_local,
- normalize_path,
- renames,
- rmtree,
-)
+from pip._internal.utils.misc import ask, is_local, normalize_path, renames, rmtree
from pip._internal.utils.temp_dir import AdjacentTempDirectory, TempDirectory
logger = getLogger(__name__)
-def _script_names(dist: Distribution, script_name: str, is_gui: bool) -> List[str]:
+def _script_names(
+ bin_dir: str, script_name: str, is_gui: bool
+) -> Generator[str, None, None]:
"""Create the fully qualified name of the files created by
{console,gui}_scripts for the given ``dist``.
Returns the list of file names
"""
- if dist_in_usersite(dist):
- bin_dir = get_bin_user()
- else:
- bin_dir = get_bin_prefix()
exe_name = os.path.join(bin_dir, script_name)
- paths_to_remove = [exe_name]
- if WINDOWS:
- paths_to_remove.append(exe_name + ".exe")
- paths_to_remove.append(exe_name + ".exe.manifest")
- if is_gui:
- paths_to_remove.append(exe_name + "-script.pyw")
- else:
- paths_to_remove.append(exe_name + "-script.py")
- return paths_to_remove
+ yield exe_name
+ if not WINDOWS:
+ return
+ yield f"{exe_name}.exe"
+ yield f"{exe_name}.exe.manifest"
+ if is_gui:
+ yield f"{exe_name}-script.pyw"
+ else:
+ yield f"{exe_name}-script.py"
-def _unique(fn: Callable[..., Iterator[Any]]) -> Callable[..., Iterator[Any]]:
+def _unique(
+ fn: Callable[..., Generator[Any, None, None]]
+) -> Callable[..., Generator[Any, None, None]]:
@functools.wraps(fn)
- def unique(*args: Any, **kw: Any) -> Iterator[Any]:
+ def unique(*args: Any, **kw: Any) -> Generator[Any, None, None]:
seen: Set[Any] = set()
for item in fn(*args, **kw):
if item not in seen:
@@ -62,7 +51,7 @@ def _unique(fn: Callable[..., Iterator[Any]]) -> Callable[..., Iterator[Any]]:
@_unique
-def uninstallation_paths(dist: Distribution) -> Iterator[str]:
+def uninstallation_paths(dist: BaseDistribution) -> Generator[str, None, None]:
"""
Yield all the uninstallation paths for dist based on RECORD-without-.py[co]
@@ -76,25 +65,25 @@ def uninstallation_paths(dist: Distribution) -> Iterator[str]:
https://packaging.python.org/specifications/recording-installed-packages/
"""
- try:
- r = csv.reader(dist.get_metadata_lines("RECORD"))
- except FileNotFoundError as missing_record_exception:
+ location = dist.location
+ assert location is not None, "not installed"
+
+ entries = dist.iter_declared_entries()
+ if entries is None:
msg = "Cannot uninstall {dist}, RECORD file not found.".format(dist=dist)
- try:
- installer = next(dist.get_metadata_lines("INSTALLER"))
- if not installer or installer == "pip":
- raise ValueError()
- except (OSError, StopIteration, ValueError):
- dep = "{}=={}".format(dist.project_name, dist.version)
+ installer = dist.installer
+ if not installer or installer == "pip":
+ dep = "{}=={}".format(dist.raw_name, dist.version)
msg += (
" You might be able to recover from this via: "
"'pip install --force-reinstall --no-deps {}'.".format(dep)
)
else:
msg += " Hint: The package was installed by {}.".format(installer)
- raise UninstallationError(msg) from missing_record_exception
- for row in r:
- path = os.path.join(dist.location, row[0])
+ raise UninstallationError(msg)
+
+ for entry in entries:
+ path = os.path.join(location, entry)
yield path
if path.endswith(".py"):
dn, fn = os.path.split(path)
@@ -317,11 +306,11 @@ class UninstallPathSet:
"""A set of file paths to be removed in the uninstallation of a
requirement."""
- def __init__(self, dist: Distribution) -> None:
- self.paths: Set[str] = set()
+ def __init__(self, dist: BaseDistribution) -> None:
+ self._paths: Set[str] = set()
self._refuse: Set[str] = set()
- self.pth: Dict[str, UninstallPthEntries] = {}
- self.dist = dist
+ self._pth: Dict[str, UninstallPthEntries] = {}
+ self._dist = dist
self._moved_paths = StashedUninstallPathSet()
def _permitted(self, path: str) -> bool:
@@ -342,7 +331,7 @@ class UninstallPathSet:
if not os.path.exists(path):
return
if self._permitted(path):
- self.paths.add(path)
+ self._paths.add(path)
else:
self._refuse.add(path)
@@ -354,37 +343,37 @@ class UninstallPathSet:
def add_pth(self, pth_file: str, entry: str) -> None:
pth_file = normalize_path(pth_file)
if self._permitted(pth_file):
- if pth_file not in self.pth:
- self.pth[pth_file] = UninstallPthEntries(pth_file)
- self.pth[pth_file].add(entry)
+ if pth_file not in self._pth:
+ self._pth[pth_file] = UninstallPthEntries(pth_file)
+ self._pth[pth_file].add(entry)
else:
self._refuse.add(pth_file)
def remove(self, auto_confirm: bool = False, verbose: bool = False) -> None:
- """Remove paths in ``self.paths`` with confirmation (unless
+ """Remove paths in ``self._paths`` with confirmation (unless
``auto_confirm`` is True)."""
- if not self.paths:
+ if not self._paths:
logger.info(
"Can't uninstall '%s'. No files were found to uninstall.",
- self.dist.project_name,
+ self._dist.raw_name,
)
return
- dist_name_version = self.dist.project_name + "-" + self.dist.version
+ dist_name_version = f"{self._dist.raw_name}-{self._dist.version}"
logger.info("Uninstalling %s:", dist_name_version)
with indent_log():
if auto_confirm or self._allowed_to_proceed(verbose):
moved = self._moved_paths
- for_rename = compress_for_rename(self.paths)
+ for_rename = compress_for_rename(self._paths)
for path in sorted(compact(for_rename)):
moved.stash(path)
logger.verbose("Removing file or directory %s", path)
- for pth in self.pth.values():
+ for pth in self._pth.values():
pth.remove()
logger.info("Successfully uninstalled %s", dist_name_version)
@@ -402,18 +391,18 @@ class UninstallPathSet:
logger.info(path)
if not verbose:
- will_remove, will_skip = compress_for_output_listing(self.paths)
+ will_remove, will_skip = compress_for_output_listing(self._paths)
else:
# In verbose mode, display all the files that are going to be
# deleted.
- will_remove = set(self.paths)
+ will_remove = set(self._paths)
will_skip = set()
_display("Would remove:", will_remove)
_display("Would not remove (might be manually added):", will_skip)
_display("Would not remove (outside of prefix):", self._refuse)
if verbose:
- _display("Will actually move:", compress_for_rename(self.paths))
+ _display("Will actually move:", compress_for_rename(self._paths))
return ask("Proceed (Y/n)? ", ("y", "n", "")) != "n"
@@ -422,12 +411,12 @@ class UninstallPathSet:
if not self._moved_paths.can_rollback:
logger.error(
"Can't roll back %s; was not uninstalled",
- self.dist.project_name,
+ self._dist.raw_name,
)
return
- logger.info("Rolling back uninstall of %s", self.dist.project_name)
+ logger.info("Rolling back uninstall of %s", self._dist.raw_name)
self._moved_paths.rollback()
- for pth in self.pth.values():
+ for pth in self._pth.values():
pth.rollback()
def commit(self) -> None:
@@ -435,142 +424,159 @@ class UninstallPathSet:
self._moved_paths.commit()
@classmethod
- def from_dist(cls, dist: Distribution) -> "UninstallPathSet":
- dist_path = normalize_path(dist.location)
- if not dist_is_local(dist):
+ def from_dist(cls, dist: BaseDistribution) -> "UninstallPathSet":
+ dist_location = dist.location
+ info_location = dist.info_location
+ if dist_location is None:
+ logger.info(
+ "Not uninstalling %s since it is not installed",
+ dist.canonical_name,
+ )
+ return cls(dist)
+
+ normalized_dist_location = normalize_path(dist_location)
+ if not dist.local:
logger.info(
"Not uninstalling %s at %s, outside environment %s",
- dist.key,
- dist_path,
+ dist.canonical_name,
+ normalized_dist_location,
sys.prefix,
)
return cls(dist)
- if dist_path in {
+ if normalized_dist_location in {
p
for p in {sysconfig.get_path("stdlib"), sysconfig.get_path("platstdlib")}
if p
}:
logger.info(
"Not uninstalling %s at %s, as it is in the standard library.",
- dist.key,
- dist_path,
+ dist.canonical_name,
+ normalized_dist_location,
)
return cls(dist)
paths_to_remove = cls(dist)
- develop_egg_link = egg_link_path_from_location(dist.project_name)
- develop_egg_link_egg_info = "{}.egg-info".format(
- pkg_resources.to_filename(dist.project_name)
+ develop_egg_link = egg_link_path_from_location(dist.raw_name)
+
+ # Distribution is installed with metadata in a "flat" .egg-info
+ # directory. This means it is not a modern .dist-info installation, an
+ # egg, or legacy editable.
+ setuptools_flat_installation = (
+ dist.installed_with_setuptools_egg_info
+ and info_location is not None
+ and os.path.exists(info_location)
+ # If dist is editable and the location points to a ``.egg-info``,
+ # we are in fact in the legacy editable case.
+ and not info_location.endswith(f"{dist.setuptools_filename}.egg-info")
)
- egg_info_exists = dist.egg_info and os.path.exists(dist.egg_info)
- # Special case for distutils installed package
- distutils_egg_info = getattr(dist._provider, "path", None)
# Uninstall cases order do matter as in the case of 2 installs of the
# same package, pip needs to uninstall the currently detected version
- if (
- egg_info_exists
- and dist.egg_info.endswith(".egg-info")
- and not dist.egg_info.endswith(develop_egg_link_egg_info)
- ):
- # if dist.egg_info.endswith(develop_egg_link_egg_info), we
- # are in fact in the develop_egg_link case
- paths_to_remove.add(dist.egg_info)
- if dist.has_metadata("installed-files.txt"):
- for installed_file in dist.get_metadata(
- "installed-files.txt"
- ).splitlines():
- path = os.path.normpath(os.path.join(dist.egg_info, installed_file))
- paths_to_remove.add(path)
+ if setuptools_flat_installation:
+ if info_location is not None:
+ paths_to_remove.add(info_location)
+ installed_files = dist.iter_declared_entries()
+ if installed_files is not None:
+ for installed_file in installed_files:
+ paths_to_remove.add(os.path.join(dist_location, installed_file))
# FIXME: need a test for this elif block
# occurs with --single-version-externally-managed/--record outside
# of pip
- elif dist.has_metadata("top_level.txt"):
- if dist.has_metadata("namespace_packages.txt"):
- namespaces = dist.get_metadata("namespace_packages.txt")
- else:
+ elif dist.is_file("top_level.txt"):
+ try:
+ namespace_packages = dist.read_text("namespace_packages.txt")
+ except FileNotFoundError:
namespaces = []
+ else:
+ namespaces = namespace_packages.splitlines(keepends=False)
for top_level_pkg in [
p
- for p in dist.get_metadata("top_level.txt").splitlines()
+ for p in dist.read_text("top_level.txt").splitlines()
if p and p not in namespaces
]:
- path = os.path.join(dist.location, top_level_pkg)
+ path = os.path.join(dist_location, top_level_pkg)
paths_to_remove.add(path)
- paths_to_remove.add(path + ".py")
- paths_to_remove.add(path + ".pyc")
- paths_to_remove.add(path + ".pyo")
+ paths_to_remove.add(f"{path}.py")
+ paths_to_remove.add(f"{path}.pyc")
+ paths_to_remove.add(f"{path}.pyo")
- elif distutils_egg_info:
+ elif dist.installed_by_distutils:
raise UninstallationError(
"Cannot uninstall {!r}. It is a distutils installed project "
"and thus we cannot accurately determine which files belong "
"to it which would lead to only a partial uninstall.".format(
- dist.project_name,
+ dist.raw_name,
)
)
- elif dist.location.endswith(".egg"):
+ elif dist.installed_as_egg:
# package installed by easy_install
# We cannot match on dist.egg_name because it can slightly vary
# i.e. setuptools-0.6c11-py2.6.egg vs setuptools-0.6rc11-py2.6.egg
- paths_to_remove.add(dist.location)
- easy_install_egg = os.path.split(dist.location)[1]
+ paths_to_remove.add(dist_location)
+ easy_install_egg = os.path.split(dist_location)[1]
easy_install_pth = os.path.join(
- os.path.dirname(dist.location), "easy-install.pth"
+ os.path.dirname(dist_location),
+ "easy-install.pth",
)
paths_to_remove.add_pth(easy_install_pth, "./" + easy_install_egg)
- elif egg_info_exists and dist.egg_info.endswith(".dist-info"):
+ elif dist.installed_with_dist_info:
for path in uninstallation_paths(dist):
paths_to_remove.add(path)
elif develop_egg_link:
- # develop egg
+ # PEP 660 modern editable is handled in the ``.dist-info`` case
+ # above, so this only covers the setuptools-style editable.
with open(develop_egg_link) as fh:
link_pointer = os.path.normcase(fh.readline().strip())
- assert link_pointer == dist.location, (
- "Egg-link located at {} and pointing to {} does not match "
- "installed location of {} at {}".format(
- develop_egg_link, link_pointer, dist.project_name, dist.location
- )
+ normalized_link_pointer = normalize_path(link_pointer)
+ assert os.path.samefile(
+ normalized_link_pointer, normalized_dist_location
+ ), (
+ f"Egg-link {develop_egg_link} (to {link_pointer}) does not match "
+ f"installed location of {dist.raw_name} (at {dist_location})"
)
paths_to_remove.add(develop_egg_link)
easy_install_pth = os.path.join(
os.path.dirname(develop_egg_link), "easy-install.pth"
)
- paths_to_remove.add_pth(easy_install_pth, dist.location)
+ paths_to_remove.add_pth(easy_install_pth, dist_location)
else:
logger.debug(
"Not sure how to uninstall: %s - Check: %s",
dist,
- dist.location,
+ dist_location,
)
+ if dist.in_usersite:
+ bin_dir = get_bin_user()
+ else:
+ bin_dir = get_bin_prefix()
+
# find distutils scripts= scripts
- if dist.has_metadata("scripts") and dist.metadata_isdir("scripts"):
- for script in dist.metadata_listdir("scripts"):
- if dist_in_usersite(dist):
- bin_dir = get_bin_user()
- else:
- bin_dir = get_bin_prefix()
+ try:
+ for script in dist.iter_distutils_script_names():
paths_to_remove.add(os.path.join(bin_dir, script))
if WINDOWS:
- paths_to_remove.add(os.path.join(bin_dir, script) + ".bat")
-
- # find console_scripts
- _scripts_to_remove = []
- console_scripts = dist.get_entry_map(group="console_scripts")
- for name in console_scripts.keys():
- _scripts_to_remove.extend(_script_names(dist, name, False))
- # find gui_scripts
- gui_scripts = dist.get_entry_map(group="gui_scripts")
- for name in gui_scripts.keys():
- _scripts_to_remove.extend(_script_names(dist, name, True))
-
- for s in _scripts_to_remove:
+ paths_to_remove.add(os.path.join(bin_dir, f"{script}.bat"))
+ except (FileNotFoundError, NotADirectoryError):
+ pass
+
+ # find console_scripts and gui_scripts
+ def iter_scripts_to_remove(
+ dist: BaseDistribution,
+ bin_dir: str,
+ ) -> Generator[str, None, None]:
+ for entry_point in dist.iter_entry_points():
+ if entry_point.group == "console_scripts":
+ yield from _script_names(bin_dir, entry_point.name, False)
+ elif entry_point.group == "gui_scripts":
+ yield from _script_names(bin_dir, entry_point.name, True)
+
+ for s in iter_scripts_to_remove(dist, bin_dir):
paths_to_remove.add(s)
return paths_to_remove
diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py
index 09caaa6d5..fb49d4169 100644
--- a/src/pip/_internal/resolution/legacy/resolver.py
+++ b/src/pip/_internal/resolution/legacy/resolver.py
@@ -28,12 +28,14 @@ from pip._internal.exceptions import (
DistributionNotFound,
HashError,
HashErrors,
+ InstallationError,
NoneMetadataError,
UnsupportedPythonVersion,
)
from pip._internal.index.package_finder import PackageFinder
from pip._internal.metadata import BaseDistribution
from pip._internal.models.link import Link
+from pip._internal.models.wheel import Wheel
from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req.req_install import (
InstallRequirement,
@@ -41,9 +43,11 @@ from pip._internal.req.req_install import (
)
from pip._internal.req.req_set import RequirementSet
from pip._internal.resolution.base import BaseResolver, InstallRequirementProvider
+from pip._internal.utils import compatibility_tags
from pip._internal.utils.compatibility_tags import get_supported
+from pip._internal.utils.direct_url_helpers import direct_url_from_link
from pip._internal.utils.logging import indent_log
-from pip._internal.utils.misc import dist_in_usersite, normalize_version_info
+from pip._internal.utils.misc import normalize_version_info
from pip._internal.utils.packaging import check_requires_python
logger = logging.getLogger(__name__)
@@ -168,7 +172,7 @@ class Resolver(BaseResolver):
for req in root_reqs:
if req.constraint:
check_invalid_constraint_type(req)
- requirement_set.add_requirement(req)
+ self._add_requirement_to_set(requirement_set, req)
# Actually prepare the files, and collect any exceptions. Most hash
# exceptions cannot be checked ahead of time, because
@@ -188,6 +192,124 @@ class Resolver(BaseResolver):
return requirement_set
+ def _add_requirement_to_set(
+ self,
+ requirement_set: RequirementSet,
+ install_req: InstallRequirement,
+ parent_req_name: Optional[str] = None,
+ extras_requested: Optional[Iterable[str]] = None,
+ ) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]:
+ """Add install_req as a requirement to install.
+
+ :param parent_req_name: The name of the requirement that needed this
+ added. The name is used because when multiple unnamed requirements
+ resolve to the same name, we could otherwise end up with dependency
+ links that point outside the Requirements set. parent_req must
+ already be added. Note that None implies that this is a user
+ supplied requirement, vs an inferred one.
+ :param extras_requested: an iterable of extras used to evaluate the
+ environment markers.
+ :return: Additional requirements to scan. That is either [] if
+ the requirement is not applicable, or [install_req] if the
+ requirement is applicable and has just been added.
+ """
+ # If the markers do not match, ignore this requirement.
+ if not install_req.match_markers(extras_requested):
+ logger.info(
+ "Ignoring %s: markers '%s' don't match your environment",
+ install_req.name,
+ install_req.markers,
+ )
+ return [], None
+
+ # If the wheel is not supported, raise an error.
+ # Should check this after filtering out based on environment markers to
+ # allow specifying different wheels based on the environment/OS, in a
+ # single requirements file.
+ if install_req.link and install_req.link.is_wheel:
+ wheel = Wheel(install_req.link.filename)
+ tags = compatibility_tags.get_supported()
+ if requirement_set.check_supported_wheels and not wheel.supported(tags):
+ raise InstallationError(
+ "{} is not a supported wheel on this platform.".format(
+ wheel.filename
+ )
+ )
+
+ # This next bit is really a sanity check.
+ assert (
+ not install_req.user_supplied or parent_req_name is None
+ ), "a user supplied req shouldn't have a parent"
+
+ # Unnamed requirements are scanned again and the requirement won't be
+ # added as a dependency until after scanning.
+ if not install_req.name:
+ requirement_set.add_unnamed_requirement(install_req)
+ return [install_req], None
+
+ try:
+ existing_req: Optional[
+ InstallRequirement
+ ] = requirement_set.get_requirement(install_req.name)
+ except KeyError:
+ existing_req = None
+
+ has_conflicting_requirement = (
+ parent_req_name is None
+ and existing_req
+ and not existing_req.constraint
+ and existing_req.extras == install_req.extras
+ and existing_req.req
+ and install_req.req
+ and existing_req.req.specifier != install_req.req.specifier
+ )
+ if has_conflicting_requirement:
+ raise InstallationError(
+ "Double requirement given: {} (already in {}, name={!r})".format(
+ install_req, existing_req, install_req.name
+ )
+ )
+
+ # When no existing requirement exists, add the requirement as a
+ # dependency and it will be scanned again after.
+ if not existing_req:
+ requirement_set.add_named_requirement(install_req)
+ # We'd want to rescan this requirement later
+ return [install_req], install_req
+
+ # Assume there's no need to scan, and that we've already
+ # encountered this for scanning.
+ if install_req.constraint or not existing_req.constraint:
+ return [], existing_req
+
+ does_not_satisfy_constraint = install_req.link and not (
+ existing_req.link and install_req.link.path == existing_req.link.path
+ )
+ if does_not_satisfy_constraint:
+ raise InstallationError(
+ "Could not satisfy constraints for '{}': "
+ "installation from path or url cannot be "
+ "constrained to a version".format(install_req.name)
+ )
+ # If we're now installing a constraint, mark the existing
+ # object for real installation.
+ existing_req.constraint = False
+ # If we're now installing a user supplied requirement,
+ # mark the existing object as such.
+ if install_req.user_supplied:
+ existing_req.user_supplied = True
+ existing_req.extras = tuple(
+ sorted(set(existing_req.extras) | set(install_req.extras))
+ )
+ logger.debug(
+ "Setting %s extras to: %s",
+ existing_req,
+ existing_req.extras,
+ )
+ # Return the existing requirement for addition to the parent and
+ # scanning again.
+ return [existing_req], existing_req
+
def _is_upgrade_allowed(self, req: InstallRequirement) -> bool:
if self.upgrade_strategy == "to-satisfy-only":
return False
@@ -203,7 +325,7 @@ class Resolver(BaseResolver):
"""
# Don't uninstall the conflict if doing a user install and the
# conflict is not a user install.
- if not self.use_user_site or dist_in_usersite(req.satisfied_by):
+ if not self.use_user_site or req.satisfied_by.in_usersite:
req.should_reinstall = True
req.satisfied_by = None
@@ -310,6 +432,14 @@ class Resolver(BaseResolver):
logger.debug("Using cached wheel link: %s", cache_entry.link)
if req.link is req.original_link and cache_entry.persistent:
req.original_link_is_in_wheel_cache = True
+ if cache_entry.origin is not None:
+ req.download_info = cache_entry.origin
+ else:
+ # Legacy cache entry that does not have origin.json.
+ # download_info may miss the archive_info.hash field.
+ req.download_info = direct_url_from_link(
+ req.link, link_is_in_wheel_cache=cache_entry.persistent
+ )
req.link = cache_entry.link
def _get_dist_for(self, req: InstallRequirement) -> BaseDistribution:
@@ -393,7 +523,8 @@ class Resolver(BaseResolver):
# the legacy resolver so I'm just not going to bother refactoring.
sub_install_req = self._make_install_req(str(subreq), req_to_install)
parent_req_name = req_to_install.name
- to_scan_again, add_to_parent = requirement_set.add_requirement(
+ to_scan_again, add_to_parent = self._add_requirement_to_set(
+ requirement_set,
sub_install_req,
parent_req_name=parent_req_name,
extras_requested=extras_requested,
@@ -410,7 +541,9 @@ class Resolver(BaseResolver):
# 'unnamed' requirements can only come from being directly
# provided by the user.
assert req_to_install.user_supplied
- requirement_set.add_requirement(req_to_install, parent_req_name=None)
+ self._add_requirement_to_set(
+ requirement_set, req_to_install, parent_req_name=None
+ )
if not self.ignore_dependencies:
if req_to_install.extras:
diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py
index 60fad55db..7f09efc15 100644
--- a/src/pip/_internal/resolution/resolvelib/candidates.py
+++ b/src/pip/_internal/resolution/resolvelib/candidates.py
@@ -5,7 +5,11 @@ from typing import TYPE_CHECKING, Any, FrozenSet, Iterable, Optional, Tuple, Uni
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
from pip._vendor.packaging.version import Version
-from pip._internal.exceptions import HashError, MetadataInconsistent
+from pip._internal.exceptions import (
+ HashError,
+ InstallationSubprocessError,
+ MetadataInconsistent,
+)
from pip._internal.metadata import BaseDistribution
from pip._internal.models.link import Link, links_equivalent
from pip._internal.models.wheel import Wheel
@@ -14,6 +18,7 @@ from pip._internal.req.constructors import (
install_req_from_line,
)
from pip._internal.req.req_install import InstallRequirement
+from pip._internal.utils.direct_url_helpers import direct_url_from_link
from pip._internal.utils.misc import normalize_version_info
from .base import Candidate, CandidateVersion, Requirement, format_name
@@ -61,10 +66,10 @@ def make_install_req_from_link(
isolated=template.isolated,
constraint=template.constraint,
options=dict(
- install_options=template.install_options,
global_options=template.global_options,
hashes=template.hash_options,
),
+ config_settings=template.config_settings,
)
ireq.original_link = template.original_link
ireq.link = link
@@ -84,18 +89,16 @@ def make_install_req_from_editable(
constraint=template.constraint,
permit_editable_wheels=template.permit_editable_wheels,
options=dict(
- install_options=template.install_options,
global_options=template.global_options,
hashes=template.hash_options,
),
+ config_settings=template.config_settings,
)
def _make_install_req_from_dist(
dist: BaseDistribution, template: InstallRequirement
) -> InstallRequirement:
- from pip._internal.metadata.pkg_resources import Distribution as _Dist
-
if template.req:
line = str(template.req)
elif template.link:
@@ -110,12 +113,12 @@ def _make_install_req_from_dist(
isolated=template.isolated,
constraint=template.constraint,
options=dict(
- install_options=template.install_options,
global_options=template.global_options,
hashes=template.hash_options,
),
+ config_settings=template.config_settings,
)
- ireq.satisfied_by = cast(_Dist, dist)._dist
+ ireq.satisfied_by = dist
return ireq
@@ -229,6 +232,11 @@ class _InstallRequirementBackedCandidate(Candidate):
# offending line to the user.
e.req = self._ireq
raise
+ except InstallationSubprocessError as exc:
+ # The output has been presented already, so don't duplicate it.
+ exc.context = "See above for output."
+ raise
+
self._check_metadata_consistency(dist)
return dist
@@ -271,12 +279,17 @@ class LinkCandidate(_InstallRequirementBackedCandidate):
version, wheel_version, name
)
- if (
- cache_entry is not None
- and cache_entry.persistent
- and template.link is template.original_link
- ):
- ireq.original_link_is_in_wheel_cache = True
+ if cache_entry is not None:
+ if cache_entry.persistent and template.link is template.original_link:
+ ireq.original_link_is_in_wheel_cache = True
+ if cache_entry.origin is not None:
+ ireq.download_info = cache_entry.origin
+ else:
+ # Legacy cache entry that does not have origin.json.
+ # download_info may miss the archive_info.hash field.
+ ireq.download_info = direct_url_from_link(
+ source_link, link_is_in_wheel_cache=cache_entry.persistent
+ )
super().__init__(
link=link,
diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py
index 6c784761c..a4c24b52a 100644
--- a/src/pip/_internal/resolution/resolvelib/factory.py
+++ b/src/pip/_internal/resolution/resolvelib/factory.py
@@ -27,7 +27,6 @@ from pip._internal.cache import CacheEntry, WheelCache
from pip._internal.exceptions import (
DistributionNotFound,
InstallationError,
- InstallationSubprocessError,
MetadataInconsistent,
UnsupportedPythonVersion,
UnsupportedWheel,
@@ -190,10 +189,16 @@ class Factory:
name=name,
version=version,
)
- except (InstallationSubprocessError, MetadataInconsistent) as e:
- logger.warning("Discarding %s. %s", link, e)
+ except MetadataInconsistent as e:
+ logger.info(
+ "Discarding [blue underline]%s[/]: [yellow]%s[reset]",
+ link,
+ e,
+ extra={"markup": True},
+ )
self._build_failures[link] = e
return None
+
base: BaseCandidate = self._editable_candidate_cache[link]
else:
if link not in self._link_candidate_cache:
@@ -205,8 +210,13 @@ class Factory:
name=name,
version=version,
)
- except (InstallationSubprocessError, MetadataInconsistent) as e:
- logger.warning("Discarding %s. %s", link, e)
+ except MetadataInconsistent as e:
+ logger.info(
+ "Discarding [blue underline]%s[/]: [yellow]%s[reset]",
+ link,
+ e,
+ extra={"markup": True},
+ )
self._build_failures[link] = e
return None
base = self._link_candidate_cache[link]
@@ -273,14 +283,27 @@ class Factory:
)
icans = list(result.iter_applicable())
- # PEP 592: Yanked releases must be ignored unless only yanked
- # releases can satisfy the version range. So if this is false,
- # all yanked icans need to be skipped.
+ # PEP 592: Yanked releases are ignored unless the specifier
+ # explicitly pins a version (via '==' or '===') that can be
+ # solely satisfied by a yanked release.
all_yanked = all(ican.link.is_yanked for ican in icans)
+ def is_pinned(specifier: SpecifierSet) -> bool:
+ for sp in specifier:
+ if sp.operator == "===":
+ return True
+ if sp.operator != "==":
+ continue
+ if sp.version.endswith(".*"):
+ continue
+ return True
+ return False
+
+ pinned = is_pinned(specifier)
+
# PackageFinder returns earlier versions first, so we reverse.
for ican in reversed(icans):
- if not all_yanked and ican.link.is_yanked:
+ if not (all_yanked and pinned) and ican.link.is_yanked:
continue
func = functools.partial(
self._make_candidate_from_link,
@@ -579,8 +602,15 @@ class Factory:
req_disp = f"{req} (from {parent.name})"
cands = self._finder.find_all_candidates(req.project_name)
+ skipped_by_requires_python = self._finder.requires_python_skipped_reasons()
versions = [str(v) for v in sorted({c.version for c in cands})]
+ if skipped_by_requires_python:
+ logger.critical(
+ "Ignored the following versions that require a different python "
+ "version: %s",
+ "; ".join(skipped_by_requires_python) or "none",
+ )
logger.critical(
"Could not find a version that satisfies the requirement %s "
"(from versions: %s)",
@@ -696,6 +726,6 @@ class Factory:
return DistributionNotFound(
"ResolutionImpossible: for help visit "
- "https://pip.pypa.io/en/latest/user_guide/"
- "#fixing-conflicting-dependencies"
+ "https://pip.pypa.io/en/latest/topics/dependency-resolution/"
+ "#dealing-with-dependency-conflicts"
)
diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py
index 579958ddd..315fb9c89 100644
--- a/src/pip/_internal/resolution/resolvelib/provider.py
+++ b/src/pip/_internal/resolution/resolvelib/provider.py
@@ -1,6 +1,15 @@
import collections
import math
-from typing import TYPE_CHECKING, Dict, Iterable, Iterator, Mapping, Sequence, Union
+from typing import (
+ TYPE_CHECKING,
+ Dict,
+ Iterable,
+ Iterator,
+ Mapping,
+ Sequence,
+ TypeVar,
+ Union,
+)
from pip._vendor.resolvelib.providers import AbstractProvider
@@ -37,6 +46,35 @@ else:
# services to those objects (access to pip's finder and preparer).
+D = TypeVar("D")
+V = TypeVar("V")
+
+
+def _get_with_identifier(
+ mapping: Mapping[str, V],
+ identifier: str,
+ default: D,
+) -> Union[D, V]:
+ """Get item from a package name lookup mapping with a resolver identifier.
+
+ This extra logic is needed when the target mapping is keyed by package
+ name, which cannot be directly looked up with an identifier (which may
+ contain requested extras). Additional logic is added to also look up a value
+ by "cleaning up" the extras from the identifier.
+ """
+ if identifier in mapping:
+ return mapping[identifier]
+ # HACK: Theoretically we should check whether this identifier is a valid
+ # "NAME[EXTRAS]" format, and parse out the name part with packaging or
+ # some regular expression. But since pip's resolver only spits out three
+ # kinds of identifiers: normalized PEP 503 names, normalized names plus
+ # extras, and Requires-Python, we can cheat a bit here.
+ name, open_bracket, _ = identifier.partition("[")
+ if open_bracket and name in mapping:
+ return mapping[name]
+ return default
+
+
class PipProvider(_ProviderBase):
"""Pip's provider implementation for resolvelib.
@@ -66,7 +104,7 @@ class PipProvider(_ProviderBase):
def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
return requirement_or_candidate.name
- def get_preference( # type: ignore
+ def get_preference(
self,
identifier: str,
resolutions: Mapping[str, Candidate],
@@ -79,21 +117,36 @@ class PipProvider(_ProviderBase):
The lower the return value is, the more preferred this group of
arguments is.
- Currently pip considers the followings in order:
+ Currently pip considers the following in order:
* Prefer if any of the known requirements is "direct", e.g. points to an
explicit URL.
* If equal, prefer if any requirement is "pinned", i.e. contains
operator ``===`` or ``==``.
* If equal, calculate an approximate "depth" and resolve requirements
- closer to the user-specified requirements first.
+ closer to the user-specified requirements first. If the depth cannot
+ by determined (eg: due to no matching parents), it is considered
+ infinite.
* Order user-specified requirements by the order they are specified.
* If equal, prefers "non-free" requirements, i.e. contains at least one
operator, such as ``>=`` or ``<``.
* If equal, order alphabetically for consistency (helps debuggability).
"""
- lookups = (r.get_candidate_lookup() for r, _ in information[identifier])
- candidate, ireqs = zip(*lookups)
+ try:
+ next(iter(information[identifier]))
+ except StopIteration:
+ # There is no information for this identifier, so there's no known
+ # candidates.
+ has_information = False
+ else:
+ has_information = True
+
+ if has_information:
+ lookups = (r.get_candidate_lookup() for r, _ in information[identifier])
+ candidate, ireqs = zip(*lookups)
+ else:
+ candidate, ireqs = None, ()
+
operators = [
specifier.operator
for specifier_set in (ireq.specifier for ireq in ireqs if ireq)
@@ -108,11 +161,14 @@ class PipProvider(_ProviderBase):
requested_order: Union[int, float] = self._user_requested[identifier]
except KeyError:
requested_order = math.inf
- parent_depths = (
- self._known_depths[parent.name] if parent is not None else 0.0
- for _, parent in information[identifier]
- )
- inferred_depth = min(d for d in parent_depths) + 1.0
+ if has_information:
+ parent_depths = (
+ self._known_depths[parent.name] if parent is not None else 0.0
+ for _, parent in information[identifier]
+ )
+ inferred_depth = min(d for d in parent_depths) + 1.0
+ else:
+ inferred_depth = math.inf
else:
inferred_depth = 1.0
self._known_depths[identifier] = inferred_depth
@@ -123,16 +179,6 @@ class PipProvider(_ProviderBase):
# free, so we always do it first to avoid needless work if it fails.
requires_python = identifier == REQUIRES_PYTHON_IDENTIFIER
- # HACK: Setuptools have a very long and solid backward compatibility
- # track record, and extremely few projects would request a narrow,
- # non-recent version range of it since that would break a lot things.
- # (Most projects specify it only to request for an installer feature,
- # which does not work, but that's another topic.) Intentionally
- # delaying Setuptools helps reduce branches the resolver has to check.
- # This serves as a temporary fix for issues like "apache-airflow[all]"
- # while we work on "proper" branch pruning techniques.
- delay_this = identifier == "setuptools"
-
# Prefer the causes of backtracking on the assumption that the problem
# resolving the dependency tree is related to the failures that caused
# the backtracking
@@ -140,7 +186,6 @@ class PipProvider(_ProviderBase):
return (
not requires_python,
- delay_this,
not direct,
not pinned,
not backtrack_cause,
@@ -150,28 +195,13 @@ class PipProvider(_ProviderBase):
identifier,
)
- def _get_constraint(self, identifier: str) -> Constraint:
- if identifier in self._constraints:
- return self._constraints[identifier]
-
- # HACK: Theoretically we should check whether this identifier is a valid
- # "NAME[EXTRAS]" format, and parse out the name part with packaging or
- # some regular expression. But since pip's resolver only spits out
- # three kinds of identifiers: normalized PEP 503 names, normalized names
- # plus extras, and Requires-Python, we can cheat a bit here.
- name, open_bracket, _ = identifier.partition("[")
- if open_bracket and name in self._constraints:
- return self._constraints[name]
-
- return Constraint.empty()
-
def find_matches(
self,
identifier: str,
requirements: Mapping[str, Iterator[Requirement]],
incompatibilities: Mapping[str, Iterator[Candidate]],
) -> Iterable[Candidate]:
- def _eligible_for_upgrade(name: str) -> bool:
+ def _eligible_for_upgrade(identifier: str) -> bool:
"""Are upgrades allowed for this project?
This checks the upgrade strategy, and whether the project was one
@@ -185,13 +215,23 @@ class PipProvider(_ProviderBase):
if self._upgrade_strategy == "eager":
return True
elif self._upgrade_strategy == "only-if-needed":
- return name in self._user_requested
+ user_order = _get_with_identifier(
+ self._user_requested,
+ identifier,
+ default=None,
+ )
+ return user_order is not None
return False
+ constraint = _get_with_identifier(
+ self._constraints,
+ identifier,
+ default=Constraint.empty(),
+ )
return self._factory.find_candidates(
identifier=identifier,
requirements=requirements,
- constraint=self._get_constraint(identifier),
+ constraint=constraint,
prefers_installed=(not _eligible_for_upgrade(identifier)),
incompatibilities=incompatibilities,
)
diff --git a/src/pip/_internal/resolution/resolvelib/reporter.py b/src/pip/_internal/resolution/resolvelib/reporter.py
index 6ced5329b..3c724238a 100644
--- a/src/pip/_internal/resolution/resolvelib/reporter.py
+++ b/src/pip/_internal/resolution/resolvelib/reporter.py
@@ -11,9 +11,9 @@ logger = getLogger(__name__)
class PipReporter(BaseReporter):
def __init__(self) -> None:
- self.backtracks_by_package: DefaultDict[str, int] = defaultdict(int)
+ self.reject_count_by_package: DefaultDict[str, int] = defaultdict(int)
- self._messages_at_backtrack = {
+ self._messages_at_reject_count = {
1: (
"pip is looking at multiple versions of {package_name} to "
"determine which version is compatible with other "
@@ -32,16 +32,28 @@ class PipReporter(BaseReporter):
),
}
- def backtracking(self, candidate: Candidate) -> None:
- self.backtracks_by_package[candidate.name] += 1
+ def rejecting_candidate(self, criterion: Any, candidate: Candidate) -> None:
+ self.reject_count_by_package[candidate.name] += 1
- count = self.backtracks_by_package[candidate.name]
- if count not in self._messages_at_backtrack:
+ count = self.reject_count_by_package[candidate.name]
+ if count not in self._messages_at_reject_count:
return
- message = self._messages_at_backtrack[count]
+ message = self._messages_at_reject_count[count]
logger.info("INFO: %s", message.format(package_name=candidate.name))
+ msg = "Will try a different candidate, due to conflict:"
+ for req_info in criterion.information:
+ req, parent = req_info.requirement, req_info.parent
+ # Inspired by Factory.get_installation_error
+ msg += "\n "
+ if parent:
+ msg += f"{parent.name} {parent.version} depends on "
+ else:
+ msg += "The user requested "
+ msg += req.format_for_error()
+ logger.debug(msg)
+
class PipDebuggingReporter(BaseReporter):
"""A reporter that does an info log for every event it sees."""
@@ -61,8 +73,8 @@ class PipDebuggingReporter(BaseReporter):
def adding_requirement(self, requirement: Requirement, parent: Candidate) -> None:
logger.info("Reporter.adding_requirement(%r, %r)", requirement, parent)
- def backtracking(self, candidate: Candidate) -> None:
- logger.info("Reporter.backtracking(%r)", candidate)
+ def rejecting_candidate(self, criterion: Any, candidate: Candidate) -> None:
+ logger.info("Reporter.rejecting_candidate(%r, %r)", criterion, candidate)
def pinning(self, candidate: Candidate) -> None:
logger.info("Reporter.pinning(%r)", candidate)
diff --git a/src/pip/_internal/resolution/resolvelib/resolver.py b/src/pip/_internal/resolution/resolvelib/resolver.py
index 12f967020..a605d6c25 100644
--- a/src/pip/_internal/resolution/resolvelib/resolver.py
+++ b/src/pip/_internal/resolution/resolvelib/resolver.py
@@ -171,17 +171,19 @@ class Resolver(BaseResolver):
get installed one-by-one.
The current implementation creates a topological ordering of the
- dependency graph, while breaking any cycles in the graph at arbitrary
- points. We make no guarantees about where the cycle would be broken,
- other than they would be broken.
+ dependency graph, giving more weight to packages with less
+ or no dependencies, while breaking any cycles in the graph at
+ arbitrary points. We make no guarantees about where the cycle
+ would be broken, other than it *would* be broken.
"""
assert self._result is not None, "must call resolve() first"
+ if not req_set.requirements:
+ # Nothing is left to install, so we do not need an order.
+ return []
+
graph = self._result.graph
- weights = get_topological_weights(
- graph,
- expected_node_count=len(self._result.mapping) + 1,
- )
+ weights = get_topological_weights(graph, set(req_set.requirements.keys()))
sorted_items = sorted(
req_set.requirements.items(),
@@ -192,23 +194,32 @@ class Resolver(BaseResolver):
def get_topological_weights(
- graph: "DirectedGraph[Optional[str]]", expected_node_count: int
+ graph: "DirectedGraph[Optional[str]]", requirement_keys: Set[str]
) -> Dict[Optional[str], int]:
"""Assign weights to each node based on how "deep" they are.
This implementation may change at any point in the future without prior
notice.
- We take the length for the longest path to any node from root, ignoring any
- paths that contain a single node twice (i.e. cycles). This is done through
- a depth-first search through the graph, while keeping track of the path to
- the node.
+ We first simplify the dependency graph by pruning any leaves and giving them
+ the highest weight: a package without any dependencies should be installed
+ first. This is done again and again in the same way, giving ever less weight
+ to the newly found leaves. The loop stops when no leaves are left: all
+ remaining packages have at least one dependency left in the graph.
+
+ Then we continue with the remaining graph, by taking the length for the
+ longest path to any node from root, ignoring any paths that contain a single
+ node twice (i.e. cycles). This is done through a depth-first search through
+ the graph, while keeping track of the path to the node.
Cycles in the graph result would result in node being revisited while also
- being it's own path. In this case, take no action. This helps ensure we
+ being on its own path. In this case, take no action. This helps ensure we
don't get stuck in a cycle.
When assigning weight, the longer path (i.e. larger length) is preferred.
+
+ We are only interested in the weights of packages that are in the
+ requirement_keys.
"""
path: Set[Optional[str]] = set()
weights: Dict[Optional[str], int] = {}
@@ -224,15 +235,49 @@ def get_topological_weights(
visit(child)
path.remove(node)
+ if node not in requirement_keys:
+ return
+
last_known_parent_count = weights.get(node, 0)
weights[node] = max(last_known_parent_count, len(path))
+ # Simplify the graph, pruning leaves that have no dependencies.
+ # This is needed for large graphs (say over 200 packages) because the
+ # `visit` function is exponentially slower then, taking minutes.
+ # See https://github.com/pypa/pip/issues/10557
+ # We will loop until we explicitly break the loop.
+ while True:
+ leaves = set()
+ for key in graph:
+ if key is None:
+ continue
+ for _child in graph.iter_children(key):
+ # This means we have at least one child
+ break
+ else:
+ # No child.
+ leaves.add(key)
+ if not leaves:
+ # We are done simplifying.
+ break
+ # Calculate the weight for the leaves.
+ weight = len(graph) - 1
+ for leaf in leaves:
+ if leaf not in requirement_keys:
+ continue
+ weights[leaf] = weight
+ # Remove the leaves from the graph, making it simpler.
+ for leaf in leaves:
+ graph.remove(leaf)
+
+ # Visit the remaining graph.
# `None` is guaranteed to be the root node by resolvelib.
visit(None)
- # Sanity checks
- assert weights[None] == 0
- assert len(weights) == expected_node_count
+ # Sanity check: all requirement keys should be in the weights,
+ # and no other keys should be in the weights.
+ difference = set(weights.keys()).difference(requirement_keys)
+ assert not difference, difference
return weights
diff --git a/src/pip/_internal/self_outdated_check.py b/src/pip/_internal/self_outdated_check.py
index 72f70fc25..41cc42c56 100644
--- a/src/pip/_internal/self_outdated_check.py
+++ b/src/pip/_internal/self_outdated_check.py
@@ -1,23 +1,34 @@
import datetime
+import functools
import hashlib
import json
import logging
import optparse
import os.path
import sys
-from typing import Any, Dict
+from dataclasses import dataclass
+from typing import Any, Callable, Dict, Optional
from pip._vendor.packaging.version import parse as parse_version
+from pip._vendor.rich.console import Group
+from pip._vendor.rich.markup import escape
+from pip._vendor.rich.text import Text
from pip._internal.index.collector import LinkCollector
from pip._internal.index.package_finder import PackageFinder
from pip._internal.metadata import get_default_environment
+from pip._internal.metadata.base import DistributionVersion
from pip._internal.models.selection_prefs import SelectionPreferences
from pip._internal.network.session import PipSession
+from pip._internal.utils.compat import WINDOWS
+from pip._internal.utils.entrypoints import (
+ get_best_invocation_for_this_pip,
+ get_best_invocation_for_this_python,
+)
from pip._internal.utils.filesystem import adjacent_tmp_file, check_path_owner, replace
from pip._internal.utils.misc import ensure_dir
-SELFCHECK_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ"
+_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ"
logger = logging.getLogger(__name__)
@@ -31,17 +42,17 @@ def _get_statefile_name(key: str) -> str:
class SelfCheckState:
def __init__(self, cache_dir: str) -> None:
- self.state: Dict[str, Any] = {}
- self.statefile_path = None
+ self._state: Dict[str, Any] = {}
+ self._statefile_path = None
# Try to load the existing state
if cache_dir:
- self.statefile_path = os.path.join(
+ self._statefile_path = os.path.join(
cache_dir, "selfcheck", _get_statefile_name(self.key)
)
try:
- with open(self.statefile_path, encoding="utf-8") as statefile:
- self.state = json.load(statefile)
+ with open(self._statefile_path, encoding="utf-8") as statefile:
+ self._state = json.load(statefile)
except (OSError, ValueError, KeyError):
# Explicitly suppressing exceptions, since we don't want to
# error out if the cache file is invalid.
@@ -51,41 +62,87 @@ class SelfCheckState:
def key(self) -> str:
return sys.prefix
- def save(self, pypi_version: str, current_time: datetime.datetime) -> None:
+ def get(self, current_time: datetime.datetime) -> Optional[str]:
+ """Check if we have a not-outdated version loaded already."""
+ if not self._state:
+ return None
+
+ if "last_check" not in self._state:
+ return None
+
+ if "pypi_version" not in self._state:
+ return None
+
+ seven_days_in_seconds = 7 * 24 * 60 * 60
+
+ # Determine if we need to refresh the state
+ last_check = datetime.datetime.strptime(self._state["last_check"], _DATE_FMT)
+ seconds_since_last_check = (current_time - last_check).total_seconds()
+ if seconds_since_last_check > seven_days_in_seconds:
+ return None
+
+ return self._state["pypi_version"]
+
+ def set(self, pypi_version: str, current_time: datetime.datetime) -> None:
# If we do not have a path to cache in, don't bother saving.
- if not self.statefile_path:
+ if not self._statefile_path:
return
# Check to make sure that we own the directory
- if not check_path_owner(os.path.dirname(self.statefile_path)):
+ if not check_path_owner(os.path.dirname(self._statefile_path)):
return
# Now that we've ensured the directory is owned by this user, we'll go
# ahead and make sure that all our directories are created.
- ensure_dir(os.path.dirname(self.statefile_path))
+ ensure_dir(os.path.dirname(self._statefile_path))
state = {
# Include the key so it's easy to tell which pip wrote the
# file.
"key": self.key,
- "last_check": current_time.strftime(SELFCHECK_DATE_FMT),
+ "last_check": current_time.strftime(_DATE_FMT),
"pypi_version": pypi_version,
}
text = json.dumps(state, sort_keys=True, separators=(",", ":"))
- with adjacent_tmp_file(self.statefile_path) as f:
+ with adjacent_tmp_file(self._statefile_path) as f:
f.write(text.encode())
try:
# Since we have a prefix-specific state file, we can just
# overwrite whatever is there, no need to check.
- replace(f.name, self.statefile_path)
+ replace(f.name, self._statefile_path)
except OSError:
# Best effort.
pass
+@dataclass
+class UpgradePrompt:
+ old: str
+ new: str
+
+ def __rich__(self) -> Group:
+ if WINDOWS:
+ pip_cmd = f"{get_best_invocation_for_this_python()} -m pip"
+ else:
+ pip_cmd = get_best_invocation_for_this_pip()
+
+ notice = "[bold][[reset][blue]notice[reset][bold]][reset]"
+ return Group(
+ Text(),
+ Text.from_markup(
+ f"{notice} A new release of pip is available: "
+ f"[red]{self.old}[reset] -> [green]{self.new}[reset]"
+ ),
+ Text.from_markup(
+ f"{notice} To update, run: "
+ f"[green]{escape(pip_cmd)} install --upgrade pip"
+ ),
+ )
+
+
def was_installed_by_pip(pkg: str) -> bool:
"""Checks whether pkg was installed by pip
@@ -96,6 +153,68 @@ def was_installed_by_pip(pkg: str) -> bool:
return dist is not None and "pip" == dist.installer
+def _get_current_remote_pip_version(
+ session: PipSession, options: optparse.Values
+) -> Optional[str]:
+ # Lets use PackageFinder to see what the latest pip version is
+ link_collector = LinkCollector.create(
+ session,
+ options=options,
+ suppress_no_index=True,
+ )
+
+ # Pass allow_yanked=False so we don't suggest upgrading to a
+ # yanked version.
+ selection_prefs = SelectionPreferences(
+ allow_yanked=False,
+ allow_all_prereleases=False, # Explicitly set to False
+ )
+
+ finder = PackageFinder.create(
+ link_collector=link_collector,
+ selection_prefs=selection_prefs,
+ )
+ best_candidate = finder.find_best_candidate("pip").best_candidate
+ if best_candidate is None:
+ return None
+
+ return str(best_candidate.version)
+
+
+def _self_version_check_logic(
+ *,
+ state: SelfCheckState,
+ current_time: datetime.datetime,
+ local_version: DistributionVersion,
+ get_remote_version: Callable[[], Optional[str]],
+) -> Optional[UpgradePrompt]:
+ remote_version_str = state.get(current_time)
+ if remote_version_str is None:
+ remote_version_str = get_remote_version()
+ if remote_version_str is None:
+ logger.debug("No remote pip version found")
+ return None
+ state.set(remote_version_str, current_time)
+
+ remote_version = parse_version(remote_version_str)
+ logger.debug("Remote version of pip: %s", remote_version)
+ logger.debug("Local version of pip: %s", local_version)
+
+ pip_installed_by_pip = was_installed_by_pip("pip")
+ logger.debug("Was pip installed by pip? %s", pip_installed_by_pip)
+ if not pip_installed_by_pip:
+ return None # Only suggest upgrade if pip is installed by pip.
+
+ local_version_is_older = (
+ local_version < remote_version
+ and local_version.base_version != remote_version.base_version
+ )
+ if local_version_is_older:
+ return UpgradePrompt(old=str(local_version), new=remote_version_str)
+
+ return None
+
+
def pip_self_version_check(session: PipSession, options: optparse.Values) -> None:
"""Check for an update for pip.
@@ -107,76 +226,17 @@ def pip_self_version_check(session: PipSession, options: optparse.Values) -> Non
if not installed_dist:
return
- pip_version = installed_dist.version
- pypi_version = None
-
try:
- state = SelfCheckState(cache_dir=options.cache_dir)
-
- current_time = datetime.datetime.utcnow()
- # Determine if we need to refresh the state
- if "last_check" in state.state and "pypi_version" in state.state:
- last_check = datetime.datetime.strptime(
- state.state["last_check"], SELFCHECK_DATE_FMT
- )
- if (current_time - last_check).total_seconds() < 7 * 24 * 60 * 60:
- pypi_version = state.state["pypi_version"]
-
- # Refresh the version if we need to or just see if we need to warn
- if pypi_version is None:
- # Lets use PackageFinder to see what the latest pip version is
- link_collector = LinkCollector.create(
- session,
- options=options,
- suppress_no_index=True,
- )
-
- # Pass allow_yanked=False so we don't suggest upgrading to a
- # yanked version.
- selection_prefs = SelectionPreferences(
- allow_yanked=False,
- allow_all_prereleases=False, # Explicitly set to False
- )
-
- finder = PackageFinder.create(
- link_collector=link_collector,
- selection_prefs=selection_prefs,
- )
- best_candidate = finder.find_best_candidate("pip").best_candidate
- if best_candidate is None:
- return
- pypi_version = str(best_candidate.version)
-
- # save that we've performed a check
- state.save(pypi_version, current_time)
-
- remote_version = parse_version(pypi_version)
-
- local_version_is_older = (
- pip_version < remote_version
- and pip_version.base_version != remote_version.base_version
- and was_installed_by_pip("pip")
- )
-
- # Determine if our pypi_version is older
- if not local_version_is_older:
- return
-
- # We cannot tell how the current pip is available in the current
- # command context, so be pragmatic here and suggest the command
- # that's always available. This does not accommodate spaces in
- # `sys.executable`.
- pip_cmd = f"{sys.executable} -m pip"
- logger.warning(
- "You are using pip version %s; however, version %s is "
- "available.\nYou should consider upgrading via the "
- "'%s install --upgrade pip' command.",
- pip_version,
- pypi_version,
- pip_cmd,
+ upgrade_prompt = _self_version_check_logic(
+ state=SelfCheckState(cache_dir=options.cache_dir),
+ current_time=datetime.datetime.utcnow(),
+ local_version=installed_dist.version,
+ get_remote_version=functools.partial(
+ _get_current_remote_pip_version, session, options
+ ),
)
+ if upgrade_prompt is not None:
+ logger.warning("[present-rich] %s", upgrade_prompt)
except Exception:
- logger.debug(
- "There was an error checking the latest version of pip",
- exc_info=True,
- )
+ logger.warning("There was an error checking the latest version of pip.")
+ logger.debug("See below for error", exc_info=True)
diff --git a/src/pip/_internal/utils/_jaraco_text.py b/src/pip/_internal/utils/_jaraco_text.py
new file mode 100644
index 000000000..e06947c05
--- /dev/null
+++ b/src/pip/_internal/utils/_jaraco_text.py
@@ -0,0 +1,109 @@
+"""Functions brought over from jaraco.text.
+
+These functions are not supposed to be used within `pip._internal`. These are
+helper functions brought over from `jaraco.text` to enable vendoring newer
+copies of `pkg_resources` without having to vendor `jaraco.text` and its entire
+dependency cone; something that our vendoring setup is not currently capable of
+handling.
+
+License reproduced from original source below:
+
+Copyright Jason R. Coombs
+
+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.
+"""
+
+import functools
+import itertools
+
+
+def _nonblank(str):
+ return str and not str.startswith("#")
+
+
+@functools.singledispatch
+def yield_lines(iterable):
+ r"""
+ Yield valid lines of a string or iterable.
+
+ >>> list(yield_lines(''))
+ []
+ >>> list(yield_lines(['foo', 'bar']))
+ ['foo', 'bar']
+ >>> list(yield_lines('foo\nbar'))
+ ['foo', 'bar']
+ >>> list(yield_lines('\nfoo\n#bar\nbaz #comment'))
+ ['foo', 'baz #comment']
+ >>> list(yield_lines(['foo\nbar', 'baz', 'bing\n\n\n']))
+ ['foo', 'bar', 'baz', 'bing']
+ """
+ return itertools.chain.from_iterable(map(yield_lines, iterable))
+
+
+@yield_lines.register(str)
+def _(text):
+ return filter(_nonblank, map(str.strip, text.splitlines()))
+
+
+def drop_comment(line):
+ """
+ Drop comments.
+
+ >>> drop_comment('foo # bar')
+ 'foo'
+
+ A hash without a space may be in a URL.
+
+ >>> drop_comment('http://example.com/foo#bar')
+ 'http://example.com/foo#bar'
+ """
+ return line.partition(" #")[0]
+
+
+def join_continuation(lines):
+ r"""
+ Join lines continued by a trailing backslash.
+
+ >>> list(join_continuation(['foo \\', 'bar', 'baz']))
+ ['foobar', 'baz']
+ >>> list(join_continuation(['foo \\', 'bar', 'baz']))
+ ['foobar', 'baz']
+ >>> list(join_continuation(['foo \\', 'bar \\', 'baz']))
+ ['foobarbaz']
+
+ Not sure why, but...
+ The character preceeding the backslash is also elided.
+
+ >>> list(join_continuation(['goo\\', 'dly']))
+ ['godly']
+
+ A terrible idea, but...
+ If no line is available to continue, suppress the lines.
+
+ >>> list(join_continuation(['foo', 'bar\\', 'baz\\']))
+ ['foo']
+ """
+ lines = iter(lines)
+ for item in lines:
+ while item.endswith("\\"):
+ try:
+ item = item[:-2].strip() + next(lines)
+ except StopIteration:
+ return
+ yield item
diff --git a/src/pip/_internal/utils/deprecation.py b/src/pip/_internal/utils/deprecation.py
index 72bd6f25a..18d68f3ef 100644
--- a/src/pip/_internal/utils/deprecation.py
+++ b/src/pip/_internal/utils/deprecation.py
@@ -118,3 +118,58 @@ def deprecated(
raise PipDeprecationWarning(message)
warnings.warn(message, category=PipDeprecationWarning, stacklevel=2)
+
+
+class LegacyInstallReason:
+ def __init__(
+ self,
+ reason: str,
+ replacement: Optional[str] = None,
+ gone_in: Optional[str] = None,
+ feature_flag: Optional[str] = None,
+ issue: Optional[int] = None,
+ emit_after_success: bool = False,
+ emit_before_install: bool = False,
+ ):
+ self._reason = reason
+ self._replacement = replacement
+ self._gone_in = gone_in
+ self._feature_flag = feature_flag
+ self._issue = issue
+ self.emit_after_success = emit_after_success
+ self.emit_before_install = emit_before_install
+
+ def emit_deprecation(self, name: str) -> None:
+ deprecated(
+ reason=self._reason.format(name=name),
+ replacement=self._replacement,
+ gone_in=self._gone_in,
+ feature_flag=self._feature_flag,
+ issue=self._issue,
+ )
+
+
+LegacyInstallReasonFailedBdistWheel = LegacyInstallReason(
+ reason=(
+ "{name} was installed using the legacy 'setup.py install' "
+ "method, because a wheel could not be built for it."
+ ),
+ replacement="to fix the wheel build issue reported above",
+ gone_in="23.1",
+ issue=8368,
+ emit_after_success=True,
+)
+
+
+LegacyInstallReasonMissingWheelPackage = LegacyInstallReason(
+ reason=(
+ "{name} is being installed using the legacy "
+ "'setup.py install' method, because it does not have a "
+ "'pyproject.toml' and the 'wheel' package "
+ "is not installed."
+ ),
+ replacement="to enable the '--use-pep517' option",
+ gone_in="23.1",
+ issue=8559,
+ emit_before_install=True,
+)
diff --git a/src/pip/_internal/utils/distutils_args.py b/src/pip/_internal/utils/distutils_args.py
deleted file mode 100644
index e4aa5b827..000000000
--- a/src/pip/_internal/utils/distutils_args.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from distutils.errors import DistutilsArgError
-from distutils.fancy_getopt import FancyGetopt
-from typing import Dict, List
-
-_options = [
- ("exec-prefix=", None, ""),
- ("home=", None, ""),
- ("install-base=", None, ""),
- ("install-data=", None, ""),
- ("install-headers=", None, ""),
- ("install-lib=", None, ""),
- ("install-platlib=", None, ""),
- ("install-purelib=", None, ""),
- ("install-scripts=", None, ""),
- ("prefix=", None, ""),
- ("root=", None, ""),
- ("user", None, ""),
-]
-
-
-# typeshed doesn't permit Tuple[str, None, str], see python/typeshed#3469.
-_distutils_getopt = FancyGetopt(_options) # type: ignore
-
-
-def parse_distutils_args(args: List[str]) -> Dict[str, str]:
- """Parse provided arguments, returning an object that has the
- matched arguments.
-
- Any unknown arguments are ignored.
- """
- result = {}
- for arg in args:
- try:
- _, match = _distutils_getopt.getopt(args=[arg])
- except DistutilsArgError:
- # We don't care about any other options, which here may be
- # considered unrecognized since our option list is not
- # exhaustive.
- pass
- else:
- result.update(match.__dict__)
- return result
diff --git a/src/pip/_internal/utils/egg_link.py b/src/pip/_internal/utils/egg_link.py
index 9e0da8d2d..eb57ed151 100644
--- a/src/pip/_internal/utils/egg_link.py
+++ b/src/pip/_internal/utils/egg_link.py
@@ -1,10 +1,7 @@
-# The following comment should be removed at some point in the future.
-# mypy: strict-optional=False
-
import os
import re
import sys
-from typing import Optional
+from typing import List, Optional
from pip._internal.locations import site_packages, user_site
from pip._internal.utils.virtualenv import (
@@ -57,7 +54,7 @@ def egg_link_path_from_location(raw_name: str) -> Optional[str]:
This method will just return the first one found.
"""
- sites = []
+ sites: List[str] = []
if running_under_virtualenv():
sites.append(site_packages)
if not virtualenv_no_global() and user_site:
diff --git a/src/pip/_internal/utils/encoding.py b/src/pip/_internal/utils/encoding.py
index 1c73f6c9a..008f06a79 100644
--- a/src/pip/_internal/utils/encoding.py
+++ b/src/pip/_internal/utils/encoding.py
@@ -14,7 +14,7 @@ BOMS: List[Tuple[bytes, str]] = [
(codecs.BOM_UTF32_LE, "utf-32-le"),
]
-ENCODING_RE = re.compile(br"coding[:=]\s*([-\w.]+)")
+ENCODING_RE = re.compile(rb"coding[:=]\s*([-\w.]+)")
def auto_decode(data: bytes) -> str:
diff --git a/src/pip/_internal/utils/entrypoints.py b/src/pip/_internal/utils/entrypoints.py
index 1504a1291..150136938 100644
--- a/src/pip/_internal/utils/entrypoints.py
+++ b/src/pip/_internal/utils/entrypoints.py
@@ -1,7 +1,23 @@
+import itertools
+import os
+import shutil
import sys
from typing import List, Optional
from pip._internal.cli.main import main
+from pip._internal.utils.compat import WINDOWS
+
+_EXECUTABLE_NAMES = [
+ "pip",
+ f"pip{sys.version_info.major}",
+ f"pip{sys.version_info.major}.{sys.version_info.minor}",
+]
+if WINDOWS:
+ _allowed_extensions = {"", ".exe"}
+ _EXECUTABLE_NAMES = [
+ "".join(parts)
+ for parts in itertools.product(_EXECUTABLE_NAMES, _allowed_extensions)
+ ]
def _wrapper(args: Optional[List[str]] = None) -> int:
@@ -25,3 +41,44 @@ def _wrapper(args: Optional[List[str]] = None) -> int:
"running pip directly.\n"
)
return main(args)
+
+
+def get_best_invocation_for_this_pip() -> str:
+ """Try to figure out the best way to invoke pip in the current environment."""
+ binary_directory = "Scripts" if WINDOWS else "bin"
+ binary_prefix = os.path.join(sys.prefix, binary_directory)
+
+ # Try to use pip[X[.Y]] names, if those executables for this environment are
+ # the first on PATH with that name.
+ path_parts = os.path.normcase(os.environ.get("PATH", "")).split(os.pathsep)
+ exe_are_in_PATH = os.path.normcase(binary_prefix) in path_parts
+ if exe_are_in_PATH:
+ for exe_name in _EXECUTABLE_NAMES:
+ found_executable = shutil.which(exe_name)
+ binary_executable = os.path.join(binary_prefix, exe_name)
+ if (
+ found_executable
+ and os.path.exists(binary_executable)
+ and os.path.samefile(
+ found_executable,
+ binary_executable,
+ )
+ ):
+ return exe_name
+
+ # Use the `-m` invocation, if there's no "nice" invocation.
+ return f"{get_best_invocation_for_this_python()} -m pip"
+
+
+def get_best_invocation_for_this_python() -> str:
+ """Try to figure out the best way to invoke the current Python."""
+ exe = sys.executable
+ exe_name = os.path.basename(exe)
+
+ # Try to use the basename, if it's the first executable.
+ found_executable = shutil.which(exe_name)
+ if found_executable and os.path.samefile(found_executable, exe):
+ return exe_name
+
+ # Use the full executable name, because we couldn't find something simpler.
+ return exe
diff --git a/src/pip/_internal/utils/filesystem.py b/src/pip/_internal/utils/filesystem.py
index b7e6191ab..83c2df75b 100644
--- a/src/pip/_internal/utils/filesystem.py
+++ b/src/pip/_internal/utils/filesystem.py
@@ -2,12 +2,10 @@ import fnmatch
import os
import os.path
import random
-import shutil
-import stat
import sys
from contextlib import contextmanager
from tempfile import NamedTemporaryFile
-from typing import Any, BinaryIO, Iterator, List, Union, cast
+from typing import Any, BinaryIO, Generator, List, Union, cast
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
@@ -42,35 +40,8 @@ def check_path_owner(path: str) -> bool:
return False # assume we don't own the path
-def copy2_fixed(src: str, dest: str) -> None:
- """Wrap shutil.copy2() but map errors copying socket files to
- SpecialFileError as expected.
-
- See also https://bugs.python.org/issue37700.
- """
- try:
- shutil.copy2(src, dest)
- except OSError:
- for f in [src, dest]:
- try:
- is_socket_file = is_socket(f)
- except OSError:
- # An error has already occurred. Another error here is not
- # a problem and we can ignore it.
- pass
- else:
- if is_socket_file:
- raise shutil.SpecialFileError(f"`{f}` is a socket")
-
- raise
-
-
-def is_socket(path: str) -> bool:
- return stat.S_ISSOCK(os.lstat(path).st_mode)
-
-
@contextmanager
-def adjacent_tmp_file(path: str, **kwargs: Any) -> Iterator[BinaryIO]:
+def adjacent_tmp_file(path: str, **kwargs: Any) -> Generator[BinaryIO, None, None]:
"""Return a file-like object pointing to a tmp file next to path.
The file is created securely and is ensured to be written to disk
diff --git a/src/pip/_internal/utils/hashes.py b/src/pip/_internal/utils/hashes.py
index 82eb035a0..76727306a 100644
--- a/src/pip/_internal/utils/hashes.py
+++ b/src/pip/_internal/utils/hashes.py
@@ -1,5 +1,5 @@
import hashlib
-from typing import TYPE_CHECKING, BinaryIO, Dict, Iterator, List
+from typing import TYPE_CHECKING, BinaryIO, Dict, Iterable, List, Optional
from pip._internal.exceptions import HashMismatch, HashMissing, InstallationError
from pip._internal.utils.misc import read_chunks
@@ -28,7 +28,7 @@ class Hashes:
"""
- def __init__(self, hashes: Dict[str, List[str]] = None) -> None:
+ def __init__(self, hashes: Optional[Dict[str, List[str]]] = None) -> None:
"""
:param hashes: A dict of algorithm names pointing to lists of allowed
hex digests
@@ -67,7 +67,7 @@ class Hashes:
"""Return whether the given hex digest is allowed."""
return hex_digest in self._allowed.get(hash_name, [])
- def check_against_chunks(self, chunks: Iterator[bytes]) -> None:
+ def check_against_chunks(self, chunks: Iterable[bytes]) -> None:
"""Check good hashes against ones built from iterable of chunks of
data.
diff --git a/src/pip/_internal/utils/logging.py b/src/pip/_internal/utils/logging.py
index a4b828a84..c10e1f4ce 100644
--- a/src/pip/_internal/utils/logging.py
+++ b/src/pip/_internal/utils/logging.py
@@ -4,28 +4,30 @@ import logging
import logging.handlers
import os
import sys
+import threading
+from dataclasses import dataclass
+from io import TextIOWrapper
from logging import Filter
-from typing import IO, Any, Callable, Iterator, Optional, TextIO, Type, cast
+from typing import Any, ClassVar, Generator, List, Optional, TextIO, Type
+
+from pip._vendor.rich.console import (
+ Console,
+ ConsoleOptions,
+ ConsoleRenderable,
+ RenderableType,
+ RenderResult,
+ RichCast,
+)
+from pip._vendor.rich.highlighter import NullHighlighter
+from pip._vendor.rich.logging import RichHandler
+from pip._vendor.rich.segment import Segment
+from pip._vendor.rich.style import Style
from pip._internal.utils._log import VERBOSE, getLogger
from pip._internal.utils.compat import WINDOWS
from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX
from pip._internal.utils.misc import ensure_dir
-try:
- import threading
-except ImportError:
- import dummy_threading as threading # type: ignore
-
-
-try:
- from pip._vendor import colorama
-# Lots of different errors can come from this, including SystemError and
-# ImportError.
-except Exception:
- colorama = None
-
-
_log_state = threading.local()
subprocess_logger = getLogger("pip.subprocessor")
@@ -50,7 +52,7 @@ def _is_broken_pipe_error(exc_class: Type[BaseException], exc: BaseException) ->
@contextlib.contextmanager
-def indent_log(num: int = 2) -> Iterator[None]:
+def indent_log(num: int = 2) -> Generator[None, None, None]:
"""
A context manager which will cause the log output to be indented for any
log messages emitted inside it.
@@ -119,78 +121,66 @@ class IndentingFormatter(logging.Formatter):
return formatted
-def _color_wrap(*colors: str) -> Callable[[str], str]:
- def wrapped(inp: str) -> str:
- return "".join(list(colors) + [inp, colorama.Style.RESET_ALL])
-
- return wrapped
-
-
-class ColorizedStreamHandler(logging.StreamHandler):
-
- # Don't build up a list of colors if we don't have colorama
- if colorama:
- COLORS = [
- # This needs to be in order from highest logging level to lowest.
- (logging.ERROR, _color_wrap(colorama.Fore.RED)),
- (logging.WARNING, _color_wrap(colorama.Fore.YELLOW)),
- ]
- else:
- COLORS = []
-
- def __init__(self, stream: Optional[TextIO] = None, no_color: bool = None) -> None:
- super().__init__(stream)
- self._no_color = no_color
-
- if WINDOWS and colorama:
- self.stream = colorama.AnsiToWin32(self.stream)
-
- def _using_stdout(self) -> bool:
- """
- Return whether the handler is using sys.stdout.
- """
- if WINDOWS and colorama:
- # Then self.stream is an AnsiToWin32 object.
- stream = cast(colorama.AnsiToWin32, self.stream)
- return stream.wrapped is sys.stdout
-
- return self.stream is sys.stdout
-
- def should_color(self) -> bool:
- # Don't colorize things if we do not have colorama or if told not to
- if not colorama or self._no_color:
- return False
-
- real_stream = (
- self.stream
- if not isinstance(self.stream, colorama.AnsiToWin32)
- else self.stream.wrapped
+@dataclass
+class IndentedRenderable:
+ renderable: RenderableType
+ indent: int
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ segments = console.render(self.renderable, options)
+ lines = Segment.split_lines(segments)
+ for line in lines:
+ yield Segment(" " * self.indent)
+ yield from line
+ yield Segment("\n")
+
+
+class RichPipStreamHandler(RichHandler):
+ KEYWORDS: ClassVar[Optional[List[str]]] = []
+
+ def __init__(self, stream: Optional[TextIO], no_color: bool) -> None:
+ super().__init__(
+ console=Console(file=stream, no_color=no_color, soft_wrap=True),
+ show_time=False,
+ show_level=False,
+ show_path=False,
+ highlighter=NullHighlighter(),
)
- # If the stream is a tty we should color it
- if hasattr(real_stream, "isatty") and real_stream.isatty():
- return True
-
- # If we have an ANSI term we should color it
- if os.environ.get("TERM") == "ANSI":
- return True
-
- # If anything else we should not color it
- return False
-
- def format(self, record: logging.LogRecord) -> str:
- msg = super().format(record)
-
- if self.should_color():
- for level, color in self.COLORS:
- if record.levelno >= level:
- msg = color(msg)
- break
-
- return msg
+ # Our custom override on Rich's logger, to make things work as we need them to.
+ def emit(self, record: logging.LogRecord) -> None:
+ style: Optional[Style] = None
+
+ # If we are given a diagnostic error to present, present it with indentation.
+ assert isinstance(record.args, tuple)
+ if record.msg == "[present-rich] %s" and len(record.args) == 1:
+ rich_renderable = record.args[0]
+ assert isinstance(
+ rich_renderable, (ConsoleRenderable, RichCast, str)
+ ), f"{rich_renderable} is not rich-console-renderable"
+
+ renderable: RenderableType = IndentedRenderable(
+ rich_renderable, indent=get_indentation()
+ )
+ else:
+ message = self.format(record)
+ renderable = self.render_message(record, message)
+ if record.levelno is not None:
+ if record.levelno >= logging.ERROR:
+ style = Style(color="red")
+ elif record.levelno >= logging.WARNING:
+ style = Style(color="yellow")
+
+ try:
+ self.console.print(renderable, overflow="ignore", crop=False, style=style)
+ except Exception:
+ self.handleError(record)
- # The logging module says handleError() can be customized.
def handleError(self, record: logging.LogRecord) -> None:
+ """Called when logging is unable to log some output."""
+
exc_class, exc = sys.exc_info()[:2]
# If a broken pipe occurred while calling write() or flush() on the
# stdout stream in logging's Handler.emit(), then raise our special
@@ -199,7 +189,7 @@ class ColorizedStreamHandler(logging.StreamHandler):
if (
exc_class
and exc
- and self._using_stdout()
+ and self.console.file is sys.stdout
and _is_broken_pipe_error(exc_class, exc)
):
raise BrokenStdoutLoggingError()
@@ -208,7 +198,7 @@ class ColorizedStreamHandler(logging.StreamHandler):
class BetterRotatingFileHandler(logging.handlers.RotatingFileHandler):
- def _open(self) -> IO[Any]:
+ def _open(self) -> TextIOWrapper:
ensure_dir(os.path.dirname(self.baseFilename))
return super()._open()
@@ -275,7 +265,7 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str])
"stderr": "ext://sys.stderr",
}
handler_classes = {
- "stream": "pip._internal.utils.logging.ColorizedStreamHandler",
+ "stream": "pip._internal.utils.logging.RichPipStreamHandler",
"file": "pip._internal.utils.logging.BetterRotatingFileHandler",
}
handlers = ["console", "console_errors", "console_subprocess"] + (
@@ -333,8 +323,8 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str])
"console_subprocess": {
"level": level,
"class": handler_classes["stream"],
- "no_color": no_color,
"stream": log_streams["stderr"],
+ "no_color": no_color,
"filters": ["restrict_to_subprocess"],
"formatter": "indent",
},
diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py
index d3e9053ef..baa1ba7ea 100644
--- a/src/pip/_internal/utils/misc.py
+++ b/src/pip/_internal/utils/misc.py
@@ -12,6 +12,7 @@ import posixpath
import shutil
import stat
import sys
+import sysconfig
import urllib.parse
from io import StringIO
from itertools import filterfalse, tee, zip_longest
@@ -21,6 +22,8 @@ from typing import (
BinaryIO,
Callable,
ContextManager,
+ Dict,
+ Generator,
Iterable,
Iterator,
List,
@@ -32,14 +35,13 @@ from typing import (
cast,
)
-from pip._vendor.pkg_resources import Distribution
+from pip._vendor.pyproject_hooks import BuildBackendHookCaller
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
from pip import __version__
-from pip._internal.exceptions import CommandError
-from pip._internal.locations import get_major_minor_version, site_packages, user_site
+from pip._internal.exceptions import CommandError, ExternallyManagedEnvironment
+from pip._internal.locations import get_major_minor_version
from pip._internal.utils.compat import WINDOWS
-from pip._internal.utils.egg_link import egg_link_path_from_location
from pip._internal.utils.virtualenv import running_under_virtualenv
__all__ = [
@@ -56,9 +58,10 @@ __all__ = [
"captured_stdout",
"ensure_dir",
"remove_auth_from_url",
+ "check_externally_managed",
+ "ConfiguredBuildBackendHookCaller",
]
-
logger = logging.getLogger(__name__)
T = TypeVar("T")
@@ -266,7 +269,9 @@ def is_installable_dir(path: str) -> bool:
return False
-def read_chunks(file: BinaryIO, size: int = io.DEFAULT_BUFFER_SIZE) -> Iterator[bytes]:
+def read_chunks(
+ file: BinaryIO, size: int = io.DEFAULT_BUFFER_SIZE
+) -> Generator[bytes, None, None]:
"""Yield pieces of data from a file-like object until EOF."""
while True:
chunk = file.read(size)
@@ -328,64 +333,6 @@ def is_local(path: str) -> bool:
return path.startswith(normalize_path(sys.prefix))
-def dist_is_local(dist: Distribution) -> bool:
- """
- Return True if given Distribution object is installed locally
- (i.e. within current virtualenv).
-
- Always True if we're not in a virtualenv.
-
- """
- return is_local(dist_location(dist))
-
-
-def dist_in_usersite(dist: Distribution) -> bool:
- """
- Return True if given Distribution is installed in user site.
- """
- return dist_location(dist).startswith(normalize_path(user_site))
-
-
-def dist_in_site_packages(dist: Distribution) -> bool:
- """
- Return True if given Distribution is installed in
- sysconfig.get_python_lib().
- """
- return dist_location(dist).startswith(normalize_path(site_packages))
-
-
-def get_distribution(req_name: str) -> Optional[Distribution]:
- """Given a requirement name, return the installed Distribution object.
-
- This searches from *all* distributions available in the environment, to
- match the behavior of ``pkg_resources.get_distribution()``.
-
- Left for compatibility until direct pkg_resources uses are refactored out.
- """
- from pip._internal.metadata import get_default_environment
- from pip._internal.metadata.pkg_resources import Distribution as _Dist
-
- dist = get_default_environment().get_distribution(req_name)
- if dist is None:
- return None
- return cast(_Dist, dist)._dist
-
-
-def dist_location(dist: Distribution) -> str:
- """
- Get the site-packages location of this distribution. Generally
- this is dist.location, except in the case of develop-installed
- packages, where dist.location is the source code location, and we
- want to know where the egg-link file is.
-
- The returned location is normalized (in particular, with symlinks removed).
- """
- egg_link = egg_link_path_from_location(dist.project_name)
- if egg_link:
- return normalize_path(egg_link)
- return normalize_path(dist.location)
-
-
def write_output(msg: Any, *args: Any) -> None:
logger.info(msg, *args)
@@ -406,7 +353,7 @@ class StreamWrapper(StringIO):
@contextlib.contextmanager
-def captured_output(stream_name: str) -> Iterator[StreamWrapper]:
+def captured_output(stream_name: str) -> Generator[StreamWrapper, None, None]:
"""Return a context manager used by captured_stdout/stdin/stderr
that temporarily replaces the sys stream *stream_name* with a StringIO.
@@ -616,9 +563,9 @@ def protect_pip_from_modification_on_windows(modifying_pip: bool) -> None:
python -m pip ...
"""
pip_names = [
- "pip.exe",
- "pip{}.exe".format(sys.version_info[0]),
- "pip{}.{}.exe".format(*sys.version_info[:2]),
+ "pip",
+ f"pip{sys.version_info.major}",
+ f"pip{sys.version_info.major}.{sys.version_info.minor}",
]
# See https://github.com/pypa/pip/issues/1299 for more discussion
@@ -635,6 +582,21 @@ def protect_pip_from_modification_on_windows(modifying_pip: bool) -> None:
)
+def check_externally_managed() -> None:
+ """Check whether the current environment is externally managed.
+
+ If the ``EXTERNALLY-MANAGED`` config file is found, the current environment
+ is considered externally managed, and an ExternallyManagedEnvironment is
+ raised.
+ """
+ if running_under_virtualenv():
+ return
+ marker = os.path.join(sysconfig.get_path("stdlib"), "EXTERNALLY-MANAGED")
+ if not os.path.isfile(marker):
+ return
+ raise ExternallyManagedEnvironment.from_config(marker)
+
+
def is_console_interactive() -> bool:
"""Is this console interactive?"""
return sys.stdin is not None and sys.stdin.isatty()
@@ -687,3 +649,91 @@ def partition(
"""
t1, t2 = tee(iterable)
return filterfalse(pred, t1), filter(pred, t2)
+
+
+class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
+ def __init__(
+ self,
+ config_holder: Any,
+ source_dir: str,
+ build_backend: str,
+ backend_path: Optional[str] = None,
+ runner: Optional[Callable[..., None]] = None,
+ python_executable: Optional[str] = None,
+ ):
+ super().__init__(
+ source_dir, build_backend, backend_path, runner, python_executable
+ )
+ self.config_holder = config_holder
+
+ def build_wheel(
+ self,
+ wheel_directory: str,
+ config_settings: Optional[Dict[str, str]] = None,
+ metadata_directory: Optional[str] = None,
+ ) -> str:
+ cs = self.config_holder.config_settings
+ return super().build_wheel(
+ wheel_directory, config_settings=cs, metadata_directory=metadata_directory
+ )
+
+ def build_sdist(
+ self, sdist_directory: str, config_settings: Optional[Dict[str, str]] = None
+ ) -> str:
+ cs = self.config_holder.config_settings
+ return super().build_sdist(sdist_directory, config_settings=cs)
+
+ def build_editable(
+ self,
+ wheel_directory: str,
+ config_settings: Optional[Dict[str, str]] = None,
+ metadata_directory: Optional[str] = None,
+ ) -> str:
+ cs = self.config_holder.config_settings
+ return super().build_editable(
+ wheel_directory, config_settings=cs, metadata_directory=metadata_directory
+ )
+
+ def get_requires_for_build_wheel(
+ self, config_settings: Optional[Dict[str, str]] = None
+ ) -> List[str]:
+ cs = self.config_holder.config_settings
+ return super().get_requires_for_build_wheel(config_settings=cs)
+
+ def get_requires_for_build_sdist(
+ self, config_settings: Optional[Dict[str, str]] = None
+ ) -> List[str]:
+ cs = self.config_holder.config_settings
+ return super().get_requires_for_build_sdist(config_settings=cs)
+
+ def get_requires_for_build_editable(
+ self, config_settings: Optional[Dict[str, str]] = None
+ ) -> List[str]:
+ cs = self.config_holder.config_settings
+ return super().get_requires_for_build_editable(config_settings=cs)
+
+ def prepare_metadata_for_build_wheel(
+ self,
+ metadata_directory: str,
+ config_settings: Optional[Dict[str, str]] = None,
+ _allow_fallback: bool = True,
+ ) -> str:
+ cs = self.config_holder.config_settings
+ return super().prepare_metadata_for_build_wheel(
+ metadata_directory=metadata_directory,
+ config_settings=cs,
+ _allow_fallback=_allow_fallback,
+ )
+
+ def prepare_metadata_for_build_editable(
+ self,
+ metadata_directory: str,
+ config_settings: Optional[Dict[str, str]] = None,
+ _allow_fallback: bool = True,
+ ) -> str:
+ cs = self.config_holder.config_settings
+ return super().prepare_metadata_for_build_editable(
+ metadata_directory=metadata_directory,
+ config_settings=cs,
+ _allow_fallback=_allow_fallback,
+ )
diff --git a/src/pip/_internal/utils/packaging.py b/src/pip/_internal/utils/packaging.py
index f100473e6..b9f6af4d1 100644
--- a/src/pip/_internal/utils/packaging.py
+++ b/src/pip/_internal/utils/packaging.py
@@ -1,16 +1,12 @@
import functools
import logging
-from email.message import Message
-from email.parser import FeedParser
-from typing import Optional, Tuple
+import re
+from typing import NewType, Optional, Tuple, cast
-from pip._vendor import pkg_resources
from pip._vendor.packaging import specifiers, version
from pip._vendor.packaging.requirements import Requirement
-from pip._vendor.pkg_resources import Distribution
-from pip._internal.exceptions import NoneMetadataError
-from pip._internal.utils.misc import display_path
+NormalizedExtra = NewType("NormalizedExtra", str)
logger = logging.getLogger(__name__)
@@ -38,41 +34,6 @@ def check_requires_python(
return python_version in requires_python_specifier
-def get_metadata(dist: Distribution) -> Message:
- """
- :raises NoneMetadataError: if the distribution reports `has_metadata()`
- True but `get_metadata()` returns None.
- """
- metadata_name = "METADATA"
- if isinstance(dist, pkg_resources.DistInfoDistribution) and dist.has_metadata(
- metadata_name
- ):
- metadata = dist.get_metadata(metadata_name)
- elif dist.has_metadata("PKG-INFO"):
- metadata_name = "PKG-INFO"
- metadata = dist.get_metadata(metadata_name)
- else:
- logger.warning("No metadata found in %s", display_path(dist.location))
- metadata = ""
-
- if metadata is None:
- raise NoneMetadataError(dist, metadata_name)
-
- feed_parser = FeedParser()
- # The following line errors out if with a "NoneType" TypeError if
- # passed metadata=None.
- feed_parser.feed(metadata)
- return feed_parser.close()
-
-
-def get_installer(dist: Distribution) -> str:
- if dist.has_metadata("INSTALLER"):
- for line in dist.get_metadata_lines("INSTALLER"):
- if line.strip():
- return line.strip()
- return ""
-
-
@functools.lru_cache(maxsize=512)
def get_requirement(req_string: str) -> Requirement:
"""Construct a packaging.Requirement object with caching"""
@@ -82,3 +43,15 @@ def get_requirement(req_string: str) -> Requirement:
# minimize repeated parsing of the same string to construct equivalent
# Requirement objects.
return Requirement(req_string)
+
+
+def safe_extra(extra: str) -> NormalizedExtra:
+ """Convert an arbitrary string to a standard 'extra' name
+
+ Any runs of non-alphanumeric characters are replaced with a single '_',
+ and the result is always lowercased.
+
+ This function is duplicated from ``pkg_resources``. Note that this is not
+ the same to either ``canonicalize_name`` or ``_egg_link_name``.
+ """
+ return cast(NormalizedExtra, re.sub("[^A-Za-z0-9.-]+", "_", extra).lower())
diff --git a/src/pip/_internal/utils/parallel.py b/src/pip/_internal/utils/parallel.py
deleted file mode 100644
index e31857732..000000000
--- a/src/pip/_internal/utils/parallel.py
+++ /dev/null
@@ -1,103 +0,0 @@
-"""Convenient parallelization of higher order functions.
-
-This module provides two helper functions, with appropriate fallbacks on
-Python 2 and on systems lacking support for synchronization mechanisms:
-
-- map_multiprocess
-- map_multithread
-
-These helpers work like Python 3's map, with two differences:
-
-- They don't guarantee the order of processing of
- the elements of the iterable.
-- The underlying process/thread pools chop the iterable into
- a number of chunks, so that for very long iterables using
- a large value for chunksize can make the job complete much faster
- than using the default value of 1.
-"""
-
-__all__ = ["map_multiprocess", "map_multithread"]
-
-from contextlib import contextmanager
-from multiprocessing import Pool as ProcessPool
-from multiprocessing import pool
-from multiprocessing.dummy import Pool as ThreadPool
-from typing import Callable, Iterable, Iterator, TypeVar, Union
-
-from pip._vendor.requests.adapters import DEFAULT_POOLSIZE
-
-Pool = Union[pool.Pool, pool.ThreadPool]
-S = TypeVar("S")
-T = TypeVar("T")
-
-# On platforms without sem_open, multiprocessing[.dummy] Pool
-# cannot be created.
-try:
- import multiprocessing.synchronize # noqa
-except ImportError:
- LACK_SEM_OPEN = True
-else:
- LACK_SEM_OPEN = False
-
-# Incredibly large timeout to work around bpo-8296 on Python 2.
-TIMEOUT = 2000000
-
-
-@contextmanager
-def closing(pool: Pool) -> Iterator[Pool]:
- """Return a context manager making sure the pool closes properly."""
- try:
- yield pool
- finally:
- # For Pool.imap*, close and join are needed
- # for the returned iterator to begin yielding.
- pool.close()
- pool.join()
- pool.terminate()
-
-
-def _map_fallback(
- func: Callable[[S], T], iterable: Iterable[S], chunksize: int = 1
-) -> Iterator[T]:
- """Make an iterator applying func to each element in iterable.
-
- This function is the sequential fallback either on Python 2
- where Pool.imap* doesn't react to KeyboardInterrupt
- or when sem_open is unavailable.
- """
- return map(func, iterable)
-
-
-def _map_multiprocess(
- func: Callable[[S], T], iterable: Iterable[S], chunksize: int = 1
-) -> Iterator[T]:
- """Chop iterable into chunks and submit them to a process pool.
-
- For very long iterables using a large value for chunksize can make
- the job complete much faster than using the default value of 1.
-
- Return an unordered iterator of the results.
- """
- with closing(ProcessPool()) as pool:
- return pool.imap_unordered(func, iterable, chunksize)
-
-
-def _map_multithread(
- func: Callable[[S], T], iterable: Iterable[S], chunksize: int = 1
-) -> Iterator[T]:
- """Chop iterable into chunks and submit them to a thread pool.
-
- For very long iterables using a large value for chunksize can make
- the job complete much faster than using the default value of 1.
-
- Return an unordered iterator of the results.
- """
- with closing(ThreadPool(DEFAULT_POOLSIZE)) as pool:
- return pool.imap_unordered(func, iterable, chunksize)
-
-
-if LACK_SEM_OPEN:
- map_multiprocess = map_multithread = _map_fallback
-else:
- map_multiprocess = _map_multiprocess
- map_multithread = _map_multithread
diff --git a/src/pip/_internal/utils/pkg_resources.py b/src/pip/_internal/utils/pkg_resources.py
deleted file mode 100644
index bd846aa97..000000000
--- a/src/pip/_internal/utils/pkg_resources.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from typing import Dict, Iterable, List
-
-from pip._vendor.pkg_resources import yield_lines
-
-
-class DictMetadata:
- """IMetadataProvider that reads metadata files from a dictionary."""
-
- def __init__(self, metadata: Dict[str, bytes]) -> None:
- self._metadata = metadata
-
- def has_metadata(self, name: str) -> bool:
- return name in self._metadata
-
- def get_metadata(self, name: str) -> str:
- try:
- return self._metadata[name].decode()
- except UnicodeDecodeError as e:
- # Mirrors handling done in pkg_resources.NullProvider.
- e.reason += f" in {name} file"
- raise
-
- def get_metadata_lines(self, name: str) -> Iterable[str]:
- return yield_lines(self.get_metadata(name))
-
- def metadata_isdir(self, name: str) -> bool:
- return False
-
- def metadata_listdir(self, name: str) -> List[str]:
- return []
-
- def run_script(self, script_name: str, namespace: str) -> None:
- pass
diff --git a/src/pip/_internal/utils/setuptools_build.py b/src/pip/_internal/utils/setuptools_build.py
index 9d65ceba4..0662915cb 100644
--- a/src/pip/_internal/utils/setuptools_build.py
+++ b/src/pip/_internal/utils/setuptools_build.py
@@ -1,26 +1,54 @@
import sys
+import textwrap
from typing import List, Optional, Sequence
# Shim to wrap setup.py invocation with setuptools
-#
-# We set sys.argv[0] to the path to the underlying setup.py file so
-# setuptools / distutils don't take the path to the setup.py to be "-c" when
-# invoking via the shim. This avoids e.g. the following manifest_maker
-# warning: "warning: manifest_maker: standard file '-c' not found".
-_SETUPTOOLS_SHIM = (
- "import io, os, sys, setuptools, tokenize; sys.argv[0] = {0!r}; __file__={0!r};"
- "f = getattr(tokenize, 'open', open)(__file__) "
- "if os.path.exists(__file__) "
- "else io.StringIO('from setuptools import setup; setup()');"
- "code = f.read().replace('\\r\\n', '\\n');"
- "f.close();"
- "exec(compile(code, __file__, 'exec'))"
-)
+# Note that __file__ is handled via two {!r} *and* %r, to ensure that paths on
+# Windows are correctly handled (it should be "C:\\Users" not "C:\Users").
+_SETUPTOOLS_SHIM = textwrap.dedent(
+ """
+ exec(compile('''
+ # This is <pip-setuptools-caller> -- a caller that pip uses to run setup.py
+ #
+ # - It imports setuptools before invoking setup.py, to enable projects that directly
+ # import from `distutils.core` to work with newer packaging standards.
+ # - It provides a clear error message when setuptools is not installed.
+ # - It sets `sys.argv[0]` to the underlying `setup.py`, when invoking `setup.py` so
+ # setuptools doesn't think the script is `-c`. This avoids the following warning:
+ # manifest_maker: standard file '-c' not found".
+ # - It generates a shim setup.py, for handling setup.cfg-only projects.
+ import os, sys, tokenize
+
+ try:
+ import setuptools
+ except ImportError as error:
+ print(
+ "ERROR: Can not execute `setup.py` since setuptools is not available in "
+ "the build environment.",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+ __file__ = %r
+ sys.argv[0] = __file__
+
+ if os.path.exists(__file__):
+ filename = __file__
+ with tokenize.open(__file__) as f:
+ setup_py_code = f.read()
+ else:
+ filename = "<auto-generated setuptools caller>"
+ setup_py_code = "from setuptools import setup; setup()"
+
+ exec(compile(setup_py_code, filename, "exec"))
+ ''' % ({!r},), "<pip-setuptools-caller>", "exec"))
+ """
+).rstrip()
def make_setuptools_shim_args(
setup_py_path: str,
- global_options: Sequence[str] = None,
+ global_options: Optional[Sequence[str]] = None,
no_user_config: bool = False,
unbuffered_output: bool = False,
) -> List[str]:
@@ -75,8 +103,8 @@ def make_setuptools_clean_args(
def make_setuptools_develop_args(
setup_py_path: str,
+ *,
global_options: Sequence[str],
- install_options: Sequence[str],
no_user_config: bool,
prefix: Optional[str],
home: Optional[str],
@@ -92,8 +120,6 @@ def make_setuptools_develop_args(
args += ["develop", "--no-deps"]
- args += install_options
-
if prefix:
args += ["--prefix", prefix]
if home is not None:
@@ -122,8 +148,8 @@ def make_setuptools_egg_info_args(
def make_setuptools_install_args(
setup_py_path: str,
+ *,
global_options: Sequence[str],
- install_options: Sequence[str],
record_filename: str,
root: Optional[str],
prefix: Optional[str],
@@ -162,6 +188,4 @@ def make_setuptools_install_args(
if header_dir:
args += ["--install-headers", header_dir]
- args += install_options
-
return args
diff --git a/src/pip/_internal/utils/subprocess.py b/src/pip/_internal/utils/subprocess.py
index f6e8b219a..1e8ff50ed 100644
--- a/src/pip/_internal/utils/subprocess.py
+++ b/src/pip/_internal/utils/subprocess.py
@@ -13,6 +13,8 @@ from typing import (
Union,
)
+from pip._vendor.rich.markup import escape
+
from pip._internal.cli.spinners import SpinnerInterface, open_spinner
from pip._internal.exceptions import InstallationSubprocessError
from pip._internal.utils.logging import VERBOSE, subprocess_logger
@@ -27,9 +29,6 @@ if TYPE_CHECKING:
CommandArgs = List[Union[str, HiddenText]]
-LOG_DIVIDER = "----------------------------------------"
-
-
def make_command(*args: Union[str, HiddenText, CommandArgs]) -> CommandArgs:
"""
Create a CommandArgs object.
@@ -69,53 +68,19 @@ def reveal_command_args(args: Union[List[str], CommandArgs]) -> List[str]:
return [arg.secret if isinstance(arg, HiddenText) else arg for arg in args]
-def make_subprocess_output_error(
- cmd_args: Union[List[str], CommandArgs],
- cwd: Optional[str],
- lines: List[str],
- exit_status: int,
-) -> str:
- """
- Create and return the error message to use to log a subprocess error
- with command output.
-
- :param lines: A list of lines, each ending with a newline.
- """
- command = format_command_args(cmd_args)
-
- # We know the joined output value ends in a newline.
- output = "".join(lines)
- msg = (
- # Use a unicode string to avoid "UnicodeEncodeError: 'ascii'
- # codec can't encode character ..." in Python 2 when a format
- # argument (e.g. `output`) has a non-ascii character.
- "Command errored out with exit status {exit_status}:\n"
- " command: {command_display}\n"
- " cwd: {cwd_display}\n"
- "Complete output ({line_count} lines):\n{output}{divider}"
- ).format(
- exit_status=exit_status,
- command_display=command,
- cwd_display=cwd,
- line_count=len(lines),
- output=output,
- divider=LOG_DIVIDER,
- )
- return msg
-
-
def call_subprocess(
cmd: Union[List[str], CommandArgs],
show_stdout: bool = False,
cwd: Optional[str] = None,
on_returncode: 'Literal["raise", "warn", "ignore"]' = "raise",
extra_ok_returncodes: Optional[Iterable[int]] = None,
- command_desc: Optional[str] = None,
extra_environ: Optional[Mapping[str, Any]] = None,
unset_environ: Optional[Iterable[str]] = None,
spinner: Optional[SpinnerInterface] = None,
log_failed_cmd: Optional[bool] = True,
stdout_only: Optional[bool] = False,
+ *,
+ command_desc: str,
) -> str:
"""
Args:
@@ -151,7 +116,7 @@ def call_subprocess(
# replaced by INFO.
if show_stdout:
# Then log the subprocess output at INFO level.
- log_subprocess = subprocess_logger.info
+ log_subprocess: Callable[..., None] = subprocess_logger.info
used_level = logging.INFO
else:
# Then log the subprocess output using VERBOSE. This also ensures
@@ -166,9 +131,6 @@ def call_subprocess(
# and we have a spinner.
use_spinner = not showing_subprocess and spinner is not None
- if command_desc is None:
- command_desc = format_command_args(cmd)
-
log_subprocess("Running command %s", command_desc)
env = os.environ.copy()
if extra_environ:
@@ -241,17 +203,25 @@ def call_subprocess(
spinner.finish("done")
if proc_had_error:
if on_returncode == "raise":
- if not showing_subprocess and log_failed_cmd:
- # Then the subprocess streams haven't been logged to the
- # console yet.
- msg = make_subprocess_output_error(
- cmd_args=cmd,
- cwd=cwd,
- lines=all_output,
- exit_status=proc.returncode,
+ error = InstallationSubprocessError(
+ command_description=command_desc,
+ exit_code=proc.returncode,
+ output_lines=all_output if not showing_subprocess else None,
+ )
+ if log_failed_cmd:
+ subprocess_logger.error("[present-rich] %s", error)
+ subprocess_logger.verbose(
+ "[bold magenta]full command[/]: [blue]%s[/]",
+ escape(format_command_args(cmd)),
+ extra={"markup": True},
+ )
+ subprocess_logger.verbose(
+ "[bold magenta]cwd[/]: %s",
+ escape(cwd or "[inherit]"),
+ extra={"markup": True},
)
- subprocess_logger.error(msg)
- raise InstallationSubprocessError(proc.returncode, command_desc)
+
+ raise error
elif on_returncode == "warn":
subprocess_logger.warning(
'Command "%s" had error code %s in %s',
@@ -269,8 +239,8 @@ def call_subprocess(
def runner_with_spinner_message(message: str) -> Callable[..., None]:
"""Provide a subprocess_runner that shows a spinner message.
- Intended for use with for pep517's Pep517HookCaller. Thus, the runner has
- an API that matches what's expected by Pep517HookCaller.subprocess_runner.
+ Intended for use with for BuildBackendHookCaller. Thus, the runner has
+ an API that matches what's expected by BuildBackendHookCaller.subprocess_runner.
"""
def runner(
@@ -281,6 +251,7 @@ def runner_with_spinner_message(message: str) -> Callable[..., None]:
with open_spinner(message) as spinner:
call_subprocess(
cmd,
+ command_desc=message,
cwd=cwd,
extra_environ=extra_environ,
spinner=spinner,
diff --git a/src/pip/_internal/utils/temp_dir.py b/src/pip/_internal/utils/temp_dir.py
index 442679a75..8ee8a1cb1 100644
--- a/src/pip/_internal/utils/temp_dir.py
+++ b/src/pip/_internal/utils/temp_dir.py
@@ -4,7 +4,7 @@ import logging
import os.path
import tempfile
from contextlib import ExitStack, contextmanager
-from typing import Any, Dict, Iterator, Optional, TypeVar, Union
+from typing import Any, Dict, Generator, Optional, TypeVar, Union
from pip._internal.utils.misc import enum, rmtree
@@ -26,7 +26,7 @@ _tempdir_manager: Optional[ExitStack] = None
@contextmanager
-def global_tempdir_manager() -> Iterator[None]:
+def global_tempdir_manager() -> Generator[None, None, None]:
global _tempdir_manager
with ExitStack() as stack:
old_tempdir_manager, _tempdir_manager = _tempdir_manager, stack
@@ -59,7 +59,7 @@ _tempdir_registry: Optional[TempDirectoryTypeRegistry] = None
@contextmanager
-def tempdir_registry() -> Iterator[TempDirectoryTypeRegistry]:
+def tempdir_registry() -> Generator[TempDirectoryTypeRegistry, None, None]:
"""Provides a scoped global tempdir registry that can be used to dictate
whether directories should be deleted.
"""
@@ -200,7 +200,7 @@ class AdjacentTempDirectory(TempDirectory):
super().__init__(delete=delete)
@classmethod
- def _generate_names(cls, name: str) -> Iterator[str]:
+ def _generate_names(cls, name: str) -> Generator[str, None, None]:
"""Generates a series of temporary names.
The algorithm replaces the leading characters in the name
diff --git a/src/pip/_internal/utils/unpacking.py b/src/pip/_internal/utils/unpacking.py
index 5f63f974b..78b5c13ce 100644
--- a/src/pip/_internal/utils/unpacking.py
+++ b/src/pip/_internal/utils/unpacking.py
@@ -188,8 +188,7 @@ def untar_file(filename: str, location: str) -> None:
ensure_dir(path)
elif member.issym():
try:
- # https://github.com/python/typeshed/issues/2673
- tar._extract_member(member, path) # type: ignore
+ tar._extract_member(member, path)
except Exception as exc:
# Some corrupt tar files seem to produce this
# (specifically bad symlinks)
diff --git a/src/pip/_internal/utils/virtualenv.py b/src/pip/_internal/utils/virtualenv.py
index c926db4c3..882e36f5c 100644
--- a/src/pip/_internal/utils/virtualenv.py
+++ b/src/pip/_internal/utils/virtualenv.py
@@ -19,7 +19,7 @@ def _running_under_venv() -> bool:
return sys.prefix != getattr(sys, "base_prefix", sys.prefix)
-def _running_under_regular_virtualenv() -> bool:
+def _running_under_legacy_virtualenv() -> bool:
"""Checks if sys.real_prefix is set.
This handles virtual environments created with pypa's virtualenv.
@@ -29,8 +29,8 @@ def _running_under_regular_virtualenv() -> bool:
def running_under_virtualenv() -> bool:
- """Return True if we're running inside a virtualenv, False otherwise."""
- return _running_under_venv() or _running_under_regular_virtualenv()
+ """True if we're running inside a virtual environment, False otherwise."""
+ return _running_under_venv() or _running_under_legacy_virtualenv()
def _get_pyvenv_cfg_lines() -> Optional[List[str]]:
@@ -77,7 +77,7 @@ def _no_global_under_venv() -> bool:
return False
-def _no_global_under_regular_virtualenv() -> bool:
+def _no_global_under_legacy_virtualenv() -> bool:
"""Check if "no-global-site-packages.txt" exists beside site.py
This mirrors logic in pypa/virtualenv for determining whether system
@@ -98,7 +98,7 @@ def virtualenv_no_global() -> bool:
if _running_under_venv():
return _no_global_under_venv()
- if _running_under_regular_virtualenv():
- return _no_global_under_regular_virtualenv()
+ if _running_under_legacy_virtualenv():
+ return _no_global_under_legacy_virtualenv()
return False
diff --git a/src/pip/_internal/utils/wheel.py b/src/pip/_internal/utils/wheel.py
index 03f00e409..e5e3f34ed 100644
--- a/src/pip/_internal/utils/wheel.py
+++ b/src/pip/_internal/utils/wheel.py
@@ -4,14 +4,12 @@
import logging
from email.message import Message
from email.parser import Parser
-from typing import Dict, Tuple
+from typing import Tuple
from zipfile import BadZipFile, ZipFile
from pip._vendor.packaging.utils import canonicalize_name
-from pip._vendor.pkg_resources import DistInfoDistribution, Distribution
from pip._internal.exceptions import UnsupportedWheel
-from pip._internal.utils.pkg_resources import DictMetadata
VERSION_COMPATIBLE = (1, 0)
@@ -19,50 +17,6 @@ VERSION_COMPATIBLE = (1, 0)
logger = logging.getLogger(__name__)
-class WheelMetadata(DictMetadata):
- """Metadata provider that maps metadata decoding exceptions to our
- internal exception type.
- """
-
- def __init__(self, metadata: Dict[str, bytes], wheel_name: str) -> None:
- super().__init__(metadata)
- self._wheel_name = wheel_name
-
- def get_metadata(self, name: str) -> str:
- try:
- return super().get_metadata(name)
- except UnicodeDecodeError as e:
- # Augment the default error with the origin of the file.
- raise UnsupportedWheel(
- f"Error decoding metadata for {self._wheel_name}: {e}"
- )
-
-
-def pkg_resources_distribution_for_wheel(
- wheel_zip: ZipFile, name: str, location: str
-) -> Distribution:
- """Get a pkg_resources distribution given a wheel.
-
- :raises UnsupportedWheel: on any errors
- """
- info_dir, _ = parse_wheel(wheel_zip, name)
-
- metadata_files = [p for p in wheel_zip.namelist() if p.startswith(f"{info_dir}/")]
-
- metadata_text: Dict[str, bytes] = {}
- for path in metadata_files:
- _, metadata_name = path.split("/", 1)
-
- try:
- metadata_text[metadata_name] = read_wheel_metadata_file(wheel_zip, path)
- except UnsupportedWheel as e:
- raise UnsupportedWheel("{} has an invalid wheel, {}".format(name, str(e)))
-
- metadata = WheelMetadata(metadata_text, location)
-
- return DistInfoDistribution(location=location, metadata=metadata, project_name=name)
-
-
def parse_wheel(wheel_zip: ZipFile, name: str) -> Tuple[str, Message]:
"""Extract information from the provided wheel, ensuring it meets basic
standards.
diff --git a/src/pip/_internal/vcs/bazaar.py b/src/pip/_internal/vcs/bazaar.py
index 82e75954a..20a17ed09 100644
--- a/src/pip/_internal/vcs/bazaar.py
+++ b/src/pip/_internal/vcs/bazaar.py
@@ -33,7 +33,9 @@ class Bazaar(VersionControl):
def get_base_rev_args(rev: str) -> List[str]:
return ["-r", rev]
- def fetch_new(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ def fetch_new(
+ self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
+ ) -> None:
rev_display = rev_options.to_display()
logger.info(
"Checking out %s%s to %s",
@@ -41,19 +43,36 @@ class Bazaar(VersionControl):
rev_display,
display_path(dest),
)
- cmd_args = make_command("branch", "-q", rev_options.to_args(), url, dest)
+ if verbosity <= 0:
+ flag = "--quiet"
+ elif verbosity == 1:
+ flag = ""
+ else:
+ flag = f"-{'v'*verbosity}"
+ cmd_args = make_command(
+ "checkout", "--lightweight", flag, rev_options.to_args(), url, dest
+ )
self.run_command(cmd_args)
def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
self.run_command(make_command("switch", url), cwd=dest)
def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
- cmd_args = make_command("pull", "-q", rev_options.to_args())
+ output = self.run_command(
+ make_command("info"), show_stdout=False, stdout_only=True, cwd=dest
+ )
+ if output.startswith("Standalone "):
+ # Older versions of pip used to create standalone branches.
+ # Convert the standalone branch to a checkout by calling "bzr bind".
+ cmd_args = make_command("bind", "-q", url)
+ self.run_command(cmd_args, cwd=dest)
+
+ cmd_args = make_command("update", "-q", rev_options.to_args())
self.run_command(cmd_args, cwd=dest)
@classmethod
def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]:
- # hotfix the URL scheme after removing bzr+ from bzr+ssh:// readd it
+ # hotfix the URL scheme after removing bzr+ from bzr+ssh:// re-add it
url, rev, user_pass = super().get_url_rev_and_auth(url)
if url.startswith("ssh://"):
url = "bzr+" + url
diff --git a/src/pip/_internal/vcs/git.py b/src/pip/_internal/vcs/git.py
index 7a78ad12d..8d1d49937 100644
--- a/src/pip/_internal/vcs/git.py
+++ b/src/pip/_internal/vcs/git.py
@@ -91,7 +91,12 @@ class Git(VersionControl):
return not is_tag_or_branch
def get_git_version(self) -> Tuple[int, ...]:
- version = self.run_command(["version"], show_stdout=False, stdout_only=True)
+ version = self.run_command(
+ ["version"],
+ command_desc="git version",
+ show_stdout=False,
+ stdout_only=True,
+ )
match = GIT_VERSION_REGEX.match(version)
if not match:
logger.warning("Can't parse git version: %s", version)
@@ -253,9 +258,17 @@ class Git(VersionControl):
return cls.get_revision(dest) == name
- def fetch_new(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ def fetch_new(
+ self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
+ ) -> None:
rev_display = rev_options.to_display()
logger.info("Cloning %s%s to %s", url, rev_display, display_path(dest))
+ if verbosity <= 0:
+ flags: Tuple[str, ...] = ("--quiet",)
+ elif verbosity == 1:
+ flags = ()
+ else:
+ flags = ("--verbose", "--progress")
if self.get_git_version() >= (2, 17):
# Git added support for partial clone in 2.17
# https://git-scm.com/docs/partial-clone
@@ -264,13 +277,13 @@ class Git(VersionControl):
make_command(
"clone",
"--filter=blob:none",
- "-q",
+ *flags,
url,
dest,
)
)
else:
- self.run_command(make_command("clone", "-q", url, dest))
+ self.run_command(make_command("clone", *flags, url, dest))
if rev_options.rev:
# Then a specific revision was requested.
diff --git a/src/pip/_internal/vcs/mercurial.py b/src/pip/_internal/vcs/mercurial.py
index 410c79d90..2a005e0af 100644
--- a/src/pip/_internal/vcs/mercurial.py
+++ b/src/pip/_internal/vcs/mercurial.py
@@ -1,7 +1,7 @@
import configparser
import logging
import os
-from typing import List, Optional
+from typing import List, Optional, Tuple
from pip._internal.exceptions import BadCommand, InstallationError
from pip._internal.utils.misc import HiddenText, display_path
@@ -33,7 +33,9 @@ class Mercurial(VersionControl):
def get_base_rev_args(rev: str) -> List[str]:
return [rev]
- def fetch_new(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ def fetch_new(
+ self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
+ ) -> None:
rev_display = rev_options.to_display()
logger.info(
"Cloning hg %s%s to %s",
@@ -41,9 +43,17 @@ class Mercurial(VersionControl):
rev_display,
display_path(dest),
)
- self.run_command(make_command("clone", "--noupdate", "-q", url, dest))
+ if verbosity <= 0:
+ flags: Tuple[str, ...] = ("--quiet",)
+ elif verbosity == 1:
+ flags = ()
+ elif verbosity == 2:
+ flags = ("--verbose",)
+ else:
+ flags = ("--verbose", "--debug")
+ self.run_command(make_command("clone", "--noupdate", *flags, url, dest))
self.run_command(
- make_command("update", "-q", rev_options.to_args()),
+ make_command("update", *flags, rev_options.to_args()),
cwd=dest,
)
diff --git a/src/pip/_internal/vcs/subversion.py b/src/pip/_internal/vcs/subversion.py
index b5b6fd5c0..16d93a67b 100644
--- a/src/pip/_internal/vcs/subversion.py
+++ b/src/pip/_internal/vcs/subversion.py
@@ -87,7 +87,7 @@ class Subversion(VersionControl):
@classmethod
def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]:
- # hotfix the URL scheme after removing svn+ from svn+ssh:// readd it
+ # hotfix the URL scheme after removing svn+ from svn+ssh:// re-add it
url, rev, user_pass = super().get_url_rev_and_auth(url)
if url.startswith("ssh://"):
url = "svn+" + url
@@ -184,7 +184,7 @@ class Subversion(VersionControl):
"""Always assume the versions don't match"""
return False
- def __init__(self, use_interactive: bool = None) -> None:
+ def __init__(self, use_interactive: Optional[bool] = None) -> None:
if use_interactive is None:
use_interactive = is_console_interactive()
self.use_interactive = use_interactive
@@ -277,7 +277,9 @@ class Subversion(VersionControl):
return []
- def fetch_new(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ def fetch_new(
+ self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
+ ) -> None:
rev_display = rev_options.to_display()
logger.info(
"Checking out %s%s to %s",
@@ -285,9 +287,13 @@ class Subversion(VersionControl):
rev_display,
display_path(dest),
)
+ if verbosity <= 0:
+ flag = "--quiet"
+ else:
+ flag = ""
cmd_args = make_command(
"checkout",
- "-q",
+ flag,
self.get_remote_call_options(),
rev_options.to_args(),
url,
diff --git a/src/pip/_internal/vcs/versioncontrol.py b/src/pip/_internal/vcs/versioncontrol.py
index 1139051f3..02bbf68e7 100644
--- a/src/pip/_internal/vcs/versioncontrol.py
+++ b/src/pip/_internal/vcs/versioncontrol.py
@@ -31,7 +31,12 @@ from pip._internal.utils.misc import (
is_installable_dir,
rmtree,
)
-from pip._internal.utils.subprocess import CommandArgs, call_subprocess, make_command
+from pip._internal.utils.subprocess import (
+ CommandArgs,
+ call_subprocess,
+ format_command_args,
+ make_command,
+)
from pip._internal.utils.urls import get_url_scheme
if TYPE_CHECKING:
@@ -458,7 +463,9 @@ class VersionControl:
"""
return cls.normalize_url(url1) == cls.normalize_url(url2)
- def fetch_new(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ def fetch_new(
+ self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
+ ) -> None:
"""
Fetch a revision from a repository, in the case that this is the
first fetch from the repository.
@@ -466,6 +473,7 @@ class VersionControl:
Args:
dest: the directory to fetch the repository to.
rev_options: a RevOptions object.
+ verbosity: verbosity level.
"""
raise NotImplementedError
@@ -498,18 +506,19 @@ class VersionControl:
"""
raise NotImplementedError
- def obtain(self, dest: str, url: HiddenText) -> None:
+ def obtain(self, dest: str, url: HiddenText, verbosity: int) -> None:
"""
Install or update in editable mode the package represented by this
VersionControl object.
:param dest: the repository directory in which to install or update.
:param url: the repository URL starting with a vcs prefix.
+ :param verbosity: verbosity level.
"""
url, rev_options = self.get_url_rev_options(url)
if not os.path.exists(dest):
- self.fetch_new(dest, url, rev_options)
+ self.fetch_new(dest, url, rev_options, verbosity=verbosity)
return
rev_display = rev_options.to_display()
@@ -565,14 +574,14 @@ class VersionControl:
if response == "w":
logger.warning("Deleting %s", display_path(dest))
rmtree(dest)
- self.fetch_new(dest, url, rev_options)
+ self.fetch_new(dest, url, rev_options, verbosity=verbosity)
return
if response == "b":
dest_dir = backup_dir(dest)
logger.warning("Backing up %s to %s", display_path(dest), dest_dir)
shutil.move(dest, dest_dir)
- self.fetch_new(dest, url, rev_options)
+ self.fetch_new(dest, url, rev_options, verbosity=verbosity)
return
# Do nothing if the response is "i".
@@ -586,16 +595,17 @@ class VersionControl:
)
self.switch(dest, url, rev_options)
- def unpack(self, location: str, url: HiddenText) -> None:
+ def unpack(self, location: str, url: HiddenText, verbosity: int) -> None:
"""
Clean up current location and download the url repository
(and vcs infos) into location
:param url: the repository URL starting with a vcs prefix.
+ :param verbosity: verbosity level.
"""
if os.path.exists(location):
rmtree(location)
- self.obtain(location, url=url)
+ self.obtain(location, url=url, verbosity=verbosity)
@classmethod
def get_remote_url(cls, location: str) -> str:
@@ -634,6 +644,8 @@ class VersionControl:
command name, and checks that the VCS is available
"""
cmd = make_command(cls.name, *cmd)
+ if command_desc is None:
+ command_desc = format_command_args(cmd)
try:
return call_subprocess(
cmd,
diff --git a/src/pip/_internal/wheel_builder.py b/src/pip/_internal/wheel_builder.py
index a9123a0f1..612c91ba3 100644
--- a/src/pip/_internal/wheel_builder.py
+++ b/src/pip/_internal/wheel_builder.py
@@ -5,7 +5,7 @@ import logging
import os.path
import re
import shutil
-from typing import Any, Callable, Iterable, List, Optional, Tuple
+from typing import Iterable, List, Optional, Tuple
from pip._vendor.packaging.utils import canonicalize_name, canonicalize_version
from pip._vendor.packaging.version import InvalidVersion, Version
@@ -19,6 +19,7 @@ from pip._internal.operations.build.wheel import build_wheel_pep517
from pip._internal.operations.build.wheel_editable import build_wheel_editable
from pip._internal.operations.build.wheel_legacy import build_wheel_legacy
from pip._internal.req.req_install import InstallRequirement
+from pip._internal.utils.deprecation import LegacyInstallReasonMissingWheelPackage
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import ensure_dir, hash_file, is_wheel_installed
from pip._internal.utils.setuptools_build import make_setuptools_clean_args
@@ -31,7 +32,6 @@ logger = logging.getLogger(__name__)
_egg_info_re = re.compile(r"([a-z0-9_.]+)-([a-z0-9_.!+-]+)", re.IGNORECASE)
-BinaryAllowedPredicate = Callable[[InstallRequirement], bool]
BuildResult = Tuple[List[InstallRequirement], List[InstallRequirement]]
@@ -46,7 +46,6 @@ def _contains_egg_info(s: str) -> bool:
def _should_build(
req: InstallRequirement,
need_wheel: bool,
- check_binary_allowed: BinaryAllowedPredicate,
) -> bool:
"""Return whether an InstallRequirement should be built into a wheel."""
if req.constraint:
@@ -77,20 +76,9 @@ def _should_build(
if req.use_pep517:
return True
- if not check_binary_allowed(req):
- logger.info(
- "Skipping wheel build for %s, due to binaries being disabled for it.",
- req.name,
- )
- return False
-
if not is_wheel_installed():
# we don't build legacy requirements if wheel is not installed
- logger.info(
- "Using legacy 'setup.py install' for %s, "
- "since package 'wheel' is not installed.",
- req.name,
- )
+ req.legacy_install_reason = LegacyInstallReasonMissingWheelPackage
return False
return True
@@ -99,16 +87,13 @@ def _should_build(
def should_build_for_wheel_command(
req: InstallRequirement,
) -> bool:
- return _should_build(req, need_wheel=True, check_binary_allowed=_always_true)
+ return _should_build(req, need_wheel=True)
def should_build_for_install_command(
req: InstallRequirement,
- check_binary_allowed: BinaryAllowedPredicate,
) -> bool:
- return _should_build(
- req, need_wheel=False, check_binary_allowed=check_binary_allowed
- )
+ return _should_build(req, need_wheel=False)
def _should_cache(
@@ -159,10 +144,6 @@ def _get_cache_dir(
return cache_dir
-def _always_true(_: Any) -> bool:
- return True
-
-
def _verify_one(req: InstallRequirement, wheel_path: str) -> None:
canonical_name = canonicalize_name(req.name or "")
w = Wheel(os.path.basename(wheel_path))
@@ -310,7 +291,9 @@ def _clean_one_legacy(req: InstallRequirement, global_options: List[str]) -> boo
logger.info("Running setup.py clean for %s", req.name)
try:
- call_subprocess(clean_args, cwd=req.source_dir)
+ call_subprocess(
+ clean_args, command_desc="python setup.py clean", cwd=req.source_dir
+ )
return True
except Exception:
logger.error("Failed cleaning build dir for %s", req.name)
@@ -352,6 +335,12 @@ def build(
req.editable and req.permit_editable_wheels,
)
if wheel_file:
+ # Record the download origin in the cache
+ if req.download_info is not None:
+ # download_info is guaranteed to be set because when we build an
+ # InstallRequirement it has been through the preparer before, but
+ # let's be cautious.
+ wheel_cache.record_download_origin(cache_dir, req.download_info)
# Update the link for this.
req.link = Link(path_to_url(wheel_file))
req.local_file_path = req.link.file_path
diff --git a/src/pip/_vendor/README.rst b/src/pip/_vendor/README.rst
index 26904ca25..077f1abf7 100644
--- a/src/pip/_vendor/README.rst
+++ b/src/pip/_vendor/README.rst
@@ -104,9 +104,6 @@ Modifications
rather than ``appdirs``.
* ``packaging`` has been modified to import its dependencies from
``pip._vendor``.
-* ``html5lib`` has been modified to import six from ``pip._vendor``, to prefer
- importing from ``collections.abc`` instead of ``collections`` and does not
- import ``xml.etree.cElementTree`` on Python 3.
* ``CacheControl`` has been modified to import its dependencies from
``pip._vendor``.
* ``requests`` has been modified to import its other dependencies from
diff --git a/src/pip/_vendor/__init__.py b/src/pip/_vendor/__init__.py
index 3843cb099..b22f7abb9 100644
--- a/src/pip/_vendor/__init__.py
+++ b/src/pip/_vendor/__init__.py
@@ -63,7 +63,6 @@ if DEBUNDLED:
vendored("colorama")
vendored("distlib")
vendored("distro")
- vendored("html5lib")
vendored("six")
vendored("six.moves")
vendored("six.moves.urllib")
@@ -106,6 +105,16 @@ if DEBUNDLED:
vendored("requests.packages.urllib3.util.timeout")
vendored("requests.packages.urllib3.util.url")
vendored("resolvelib")
+ vendored("rich")
+ vendored("rich.console")
+ vendored("rich.highlighter")
+ vendored("rich.logging")
+ vendored("rich.markup")
+ vendored("rich.progress")
+ vendored("rich.segment")
+ vendored("rich.style")
+ vendored("rich.text")
+ vendored("rich.traceback")
vendored("tenacity")
vendored("tomli")
vendored("urllib3")
diff --git a/src/pip/_vendor/cachecontrol/LICENSE.txt b/src/pip/_vendor/cachecontrol/LICENSE.txt
index 1ed31ac36..d8b3b56d3 100644
--- a/src/pip/_vendor/cachecontrol/LICENSE.txt
+++ b/src/pip/_vendor/cachecontrol/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright 2015 Eric Larson
+Copyright 2012-2021 Eric Larson
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -8,8 +8,6 @@ You may obtain a copy of the License at
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-implied.
-
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
diff --git a/src/pip/_vendor/cachecontrol/__init__.py b/src/pip/_vendor/cachecontrol/__init__.py
index a1bbbbe3b..f631ae6df 100644
--- a/src/pip/_vendor/cachecontrol/__init__.py
+++ b/src/pip/_vendor/cachecontrol/__init__.py
@@ -1,11 +1,18 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
"""CacheControl import Interface.
Make it easy to import from cachecontrol without long namespaces.
"""
__author__ = "Eric Larson"
__email__ = "eric@ionrock.org"
-__version__ = "0.12.6"
+__version__ = "0.12.11"
from .wrapper import CacheControl
from .adapter import CacheControlAdapter
from .controller import CacheController
+
+import logging
+logging.getLogger(__name__).addHandler(logging.NullHandler())
diff --git a/src/pip/_vendor/cachecontrol/_cmd.py b/src/pip/_vendor/cachecontrol/_cmd.py
index f1e0ad94a..4266b5ee9 100644
--- a/src/pip/_vendor/cachecontrol/_cmd.py
+++ b/src/pip/_vendor/cachecontrol/_cmd.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
import logging
from pip._vendor import requests
diff --git a/src/pip/_vendor/cachecontrol/adapter.py b/src/pip/_vendor/cachecontrol/adapter.py
index 815650e81..94c75e1a0 100644
--- a/src/pip/_vendor/cachecontrol/adapter.py
+++ b/src/pip/_vendor/cachecontrol/adapter.py
@@ -1,16 +1,20 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
import types
import functools
import zlib
from pip._vendor.requests.adapters import HTTPAdapter
-from .controller import CacheController
+from .controller import CacheController, PERMANENT_REDIRECT_STATUSES
from .cache import DictCache
from .filewrapper import CallbackFileWrapper
class CacheControlAdapter(HTTPAdapter):
- invalidating_methods = {"PUT", "DELETE"}
+ invalidating_methods = {"PUT", "PATCH", "DELETE"}
def __init__(
self,
@@ -93,7 +97,7 @@ class CacheControlAdapter(HTTPAdapter):
response = cached_response
# We always cache the 301 responses
- elif response.status == 301:
+ elif int(response.status) in PERMANENT_REDIRECT_STATUSES:
self.controller.cache_response(request, response)
else:
# Wrap the response file with a wrapper that will cache the
diff --git a/src/pip/_vendor/cachecontrol/cache.py b/src/pip/_vendor/cachecontrol/cache.py
index 94e07732d..2a965f595 100644
--- a/src/pip/_vendor/cachecontrol/cache.py
+++ b/src/pip/_vendor/cachecontrol/cache.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
"""
The cache object API for implementing caches. The default is a thread
safe in-memory dictionary.
@@ -10,7 +14,7 @@ class BaseCache(object):
def get(self, key):
raise NotImplementedError()
- def set(self, key, value):
+ def set(self, key, value, expires=None):
raise NotImplementedError()
def delete(self, key):
@@ -29,7 +33,7 @@ class DictCache(BaseCache):
def get(self, key):
return self.data.get(key, None)
- def set(self, key, value):
+ def set(self, key, value, expires=None):
with self.lock:
self.data.update({key: value})
@@ -37,3 +41,25 @@ class DictCache(BaseCache):
with self.lock:
if key in self.data:
self.data.pop(key)
+
+
+class SeparateBodyBaseCache(BaseCache):
+ """
+ In this variant, the body is not stored mixed in with the metadata, but is
+ passed in (as a bytes-like object) in a separate call to ``set_body()``.
+
+ That is, the expected interaction pattern is::
+
+ cache.set(key, serialized_metadata)
+ cache.set_body(key)
+
+ Similarly, the body should be loaded separately via ``get_body()``.
+ """
+ def set_body(self, key, body):
+ raise NotImplementedError()
+
+ def get_body(self, key):
+ """
+ Return the body as file-like object.
+ """
+ raise NotImplementedError()
diff --git a/src/pip/_vendor/cachecontrol/caches/__init__.py b/src/pip/_vendor/cachecontrol/caches/__init__.py
index 0e1658fa5..37827291f 100644
--- a/src/pip/_vendor/cachecontrol/caches/__init__.py
+++ b/src/pip/_vendor/cachecontrol/caches/__init__.py
@@ -1,2 +1,9 @@
-from .file_cache import FileCache # noqa
-from .redis_cache import RedisCache # noqa
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
+from .file_cache import FileCache, SeparateBodyFileCache
+from .redis_cache import RedisCache
+
+
+__all__ = ["FileCache", "SeparateBodyFileCache", "RedisCache"]
diff --git a/src/pip/_vendor/cachecontrol/caches/file_cache.py b/src/pip/_vendor/cachecontrol/caches/file_cache.py
index 607b94524..f1ddb2ebd 100644
--- a/src/pip/_vendor/cachecontrol/caches/file_cache.py
+++ b/src/pip/_vendor/cachecontrol/caches/file_cache.py
@@ -1,8 +1,12 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
import hashlib
import os
from textwrap import dedent
-from ..cache import BaseCache
+from ..cache import BaseCache, SeparateBodyBaseCache
from ..controller import CacheController
try:
@@ -53,7 +57,8 @@ def _secure_open_write(filename, fmode):
raise
-class FileCache(BaseCache):
+class _FileCacheMixin:
+ """Shared implementation for both FileCache variants."""
def __init__(
self,
@@ -114,22 +119,27 @@ class FileCache(BaseCache):
except FileNotFoundError:
return None
- def set(self, key, value):
+ def set(self, key, value, expires=None):
name = self._fn(key)
+ self._write(name, value)
+ def _write(self, path, data: bytes):
+ """
+ Safely write the data to the given path.
+ """
# Make sure the directory exists
try:
- os.makedirs(os.path.dirname(name), self.dirmode)
+ os.makedirs(os.path.dirname(path), self.dirmode)
except (IOError, OSError):
pass
- with self.lock_class(name) as lock:
+ with self.lock_class(path) as lock:
# Write our actual file
with _secure_open_write(lock.path, self.filemode) as fh:
- fh.write(value)
+ fh.write(data)
- def delete(self, key):
- name = self._fn(key)
+ def _delete(self, key, suffix):
+ name = self._fn(key) + suffix
if not self.forever:
try:
os.remove(name)
@@ -137,6 +147,38 @@ class FileCache(BaseCache):
pass
+class FileCache(_FileCacheMixin, BaseCache):
+ """
+ Traditional FileCache: body is stored in memory, so not suitable for large
+ downloads.
+ """
+
+ def delete(self, key):
+ self._delete(key, "")
+
+
+class SeparateBodyFileCache(_FileCacheMixin, SeparateBodyBaseCache):
+ """
+ Memory-efficient FileCache: body is stored in a separate file, reducing
+ peak memory usage.
+ """
+
+ def get_body(self, key):
+ name = self._fn(key) + ".body"
+ try:
+ return open(name, "rb")
+ except FileNotFoundError:
+ return None
+
+ def set_body(self, key, body):
+ name = self._fn(key) + ".body"
+ self._write(name, body)
+
+ def delete(self, key):
+ self._delete(key, "")
+ self._delete(key, ".body")
+
+
def url_to_file_path(url, filecache):
"""Return the file cache path based on the URL.
diff --git a/src/pip/_vendor/cachecontrol/caches/redis_cache.py b/src/pip/_vendor/cachecontrol/caches/redis_cache.py
index ed705ce7d..2cba4b070 100644
--- a/src/pip/_vendor/cachecontrol/caches/redis_cache.py
+++ b/src/pip/_vendor/cachecontrol/caches/redis_cache.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
from __future__ import division
from datetime import datetime
@@ -15,9 +19,11 @@ class RedisCache(BaseCache):
def set(self, key, value, expires=None):
if not expires:
self.conn.set(key, value)
- else:
+ elif isinstance(expires, datetime):
expires = expires - datetime.utcnow()
self.conn.setex(key, int(expires.total_seconds()), value)
+ else:
+ self.conn.setex(key, expires, value)
def delete(self, key):
self.conn.delete(key)
diff --git a/src/pip/_vendor/cachecontrol/compat.py b/src/pip/_vendor/cachecontrol/compat.py
index 33b5aed0a..ccec9379d 100644
--- a/src/pip/_vendor/cachecontrol/compat.py
+++ b/src/pip/_vendor/cachecontrol/compat.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
try:
from urllib.parse import urljoin
except ImportError:
@@ -9,7 +13,6 @@ try:
except ImportError:
import pickle
-
# Handle the case where the requests module has been patched to not have
# urllib3 bundled as part of its source.
try:
diff --git a/src/pip/_vendor/cachecontrol/controller.py b/src/pip/_vendor/cachecontrol/controller.py
index dafe55ca7..7f23529f1 100644
--- a/src/pip/_vendor/cachecontrol/controller.py
+++ b/src/pip/_vendor/cachecontrol/controller.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
"""
The httplib2 algorithms ported for use with requests.
"""
@@ -9,7 +13,7 @@ from email.utils import parsedate_tz
from pip._vendor.requests.structures import CaseInsensitiveDict
-from .cache import DictCache
+from .cache import DictCache, SeparateBodyBaseCache
from .serialize import Serializer
@@ -17,19 +21,20 @@ logger = logging.getLogger(__name__)
URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?")
+PERMANENT_REDIRECT_STATUSES = (301, 308)
+
def parse_uri(uri):
"""Parses a URI using the regex given in Appendix B of RFC 3986.
- (scheme, authority, path, query, fragment) = parse_uri(uri)
+ (scheme, authority, path, query, fragment) = parse_uri(uri)
"""
groups = URI.match(uri).groups()
return (groups[1], groups[3], groups[4], groups[6], groups[8])
class CacheController(object):
- """An interface to see if request should cached or not.
- """
+ """An interface to see if request should cached or not."""
def __init__(
self, cache=None, cache_etags=True, serializer=None, status_codes=None
@@ -37,7 +42,7 @@ class CacheController(object):
self.cache = DictCache() if cache is None else cache
self.cache_etags = cache_etags
self.serializer = serializer or Serializer()
- self.cacheable_status_codes = status_codes or (200, 203, 300, 301)
+ self.cacheable_status_codes = status_codes or (200, 203, 300, 301, 308)
@classmethod
def _urlnorm(cls, uri):
@@ -141,23 +146,29 @@ class CacheController(object):
logger.debug("No cache entry available")
return False
+ if isinstance(self.cache, SeparateBodyBaseCache):
+ body_file = self.cache.get_body(cache_url)
+ else:
+ body_file = None
+
# Check whether it can be deserialized
- resp = self.serializer.loads(request, cache_data)
+ resp = self.serializer.loads(request, cache_data, body_file)
if not resp:
logger.warning("Cache entry deserialization failed, entry ignored")
return False
- # If we have a cached 301, return it immediately. We don't
- # need to test our response for other headers b/c it is
+ # If we have a cached permanent redirect, return it immediately. We
+ # don't need to test our response for other headers b/c it is
# intrinsically "cacheable" as it is Permanent.
+ #
# See:
# https://tools.ietf.org/html/rfc7231#section-6.4.2
#
# Client can try to refresh the value by repeating the request
# with cache busting headers as usual (ie no-cache).
- if resp.status == 301:
+ if int(resp.status) in PERMANENT_REDIRECT_STATUSES:
msg = (
- 'Returning cached "301 Moved Permanently" response '
+ "Returning cached permanent redirect response "
"(ignoring date and etag information)"
)
logger.debug(msg)
@@ -244,6 +255,26 @@ class CacheController(object):
return new_headers
+ def _cache_set(self, cache_url, request, response, body=None, expires_time=None):
+ """
+ Store the data in the cache.
+ """
+ if isinstance(self.cache, SeparateBodyBaseCache):
+ # We pass in the body separately; just put a placeholder empty
+ # string in the metadata.
+ self.cache.set(
+ cache_url,
+ self.serializer.dumps(request, response, b""),
+ expires=expires_time,
+ )
+ self.cache.set_body(cache_url, body)
+ else:
+ self.cache.set(
+ cache_url,
+ self.serializer.dumps(request, response, body),
+ expires=expires_time,
+ )
+
def cache_response(self, request, response, body=None, status_codes=None):
"""
Algorithm for caching requests.
@@ -261,6 +292,11 @@ class CacheController(object):
response_headers = CaseInsensitiveDict(response.headers)
+ if "date" in response_headers:
+ date = calendar.timegm(parsedate_tz(response_headers["date"]))
+ else:
+ date = 0
+
# If we've been given a body, our response has a Content-Length, that
# Content-Length is valid then we can check to see if the body we've
# been given matches the expected size, and if it doesn't we'll just
@@ -304,35 +340,62 @@ class CacheController(object):
# If we've been given an etag, then keep the response
if self.cache_etags and "etag" in response_headers:
+ expires_time = 0
+ if response_headers.get("expires"):
+ expires = parsedate_tz(response_headers["expires"])
+ if expires is not None:
+ expires_time = calendar.timegm(expires) - date
+
+ expires_time = max(expires_time, 14 * 86400)
+
+ logger.debug("etag object cached for {0} seconds".format(expires_time))
logger.debug("Caching due to etag")
- self.cache.set(
- cache_url, self.serializer.dumps(request, response, body=body)
- )
+ self._cache_set(cache_url, request, response, body, expires_time)
- # Add to the cache any 301s. We do this before looking that
- # the Date headers.
- elif response.status == 301:
- logger.debug("Caching permanant redirect")
- self.cache.set(cache_url, self.serializer.dumps(request, response))
+ # Add to the cache any permanent redirects. We do this before looking
+ # that the Date headers.
+ elif int(response.status) in PERMANENT_REDIRECT_STATUSES:
+ logger.debug("Caching permanent redirect")
+ self._cache_set(cache_url, request, response, b"")
# Add to the cache if the response headers demand it. If there
# is no date header then we can't do anything about expiring
# the cache.
elif "date" in response_headers:
+ date = calendar.timegm(parsedate_tz(response_headers["date"]))
# cache when there is a max-age > 0
if "max-age" in cc and cc["max-age"] > 0:
logger.debug("Caching b/c date exists and max-age > 0")
- self.cache.set(
- cache_url, self.serializer.dumps(request, response, body=body)
+ expires_time = cc["max-age"]
+ self._cache_set(
+ cache_url,
+ request,
+ response,
+ body,
+ expires_time,
)
# If the request can expire, it means we should cache it
# in the meantime.
elif "expires" in response_headers:
if response_headers["expires"]:
- logger.debug("Caching b/c of expires header")
- self.cache.set(
- cache_url, self.serializer.dumps(request, response, body=body)
+ expires = parsedate_tz(response_headers["expires"])
+ if expires is not None:
+ expires_time = calendar.timegm(expires) - date
+ else:
+ expires_time = None
+
+ logger.debug(
+ "Caching b/c of expires header. expires in {0} seconds".format(
+ expires_time
+ )
+ )
+ self._cache_set(
+ cache_url,
+ request,
+ response,
+ body,
+ expires_time,
)
def update_cached_response(self, request, response):
@@ -371,6 +434,6 @@ class CacheController(object):
cached_response.status = 200
# update our cache
- self.cache.set(cache_url, self.serializer.dumps(request, cached_response))
+ self._cache_set(cache_url, request, cached_response)
return cached_response
diff --git a/src/pip/_vendor/cachecontrol/filewrapper.py b/src/pip/_vendor/cachecontrol/filewrapper.py
index 30ed4c5a6..f5ed5f6f6 100644
--- a/src/pip/_vendor/cachecontrol/filewrapper.py
+++ b/src/pip/_vendor/cachecontrol/filewrapper.py
@@ -1,4 +1,9 @@
-from io import BytesIO
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
+from tempfile import NamedTemporaryFile
+import mmap
class CallbackFileWrapper(object):
@@ -11,10 +16,17 @@ class CallbackFileWrapper(object):
This class uses members with a double underscore (__) leading prefix so as
not to accidentally shadow an attribute.
+
+ The data is stored in a temporary file until it is all available. As long
+ as the temporary files directory is disk-based (sometimes it's a
+ memory-backed-``tmpfs`` on Linux), data will be unloaded to disk if memory
+ pressure is high. For small files the disk usually won't be used at all,
+ it'll all be in the filesystem memory cache, so there should be no
+ performance impact.
"""
def __init__(self, fp, callback):
- self.__buf = BytesIO()
+ self.__buf = NamedTemporaryFile("rb+", delete=True)
self.__fp = fp
self.__callback = callback
@@ -49,7 +61,19 @@ class CallbackFileWrapper(object):
def _close(self):
if self.__callback:
- self.__callback(self.__buf.getvalue())
+ if self.__buf.tell() == 0:
+ # Empty file:
+ result = b""
+ else:
+ # Return the data without actually loading it into memory,
+ # relying on Python's buffer API and mmap(). mmap() just gives
+ # a view directly into the filesystem's memory cache, so it
+ # doesn't result in duplicate memory use.
+ self.__buf.seek(0, 0)
+ result = memoryview(
+ mmap.mmap(self.__buf.fileno(), 0, access=mmap.ACCESS_READ)
+ )
+ self.__callback(result)
# We assign this to None here, because otherwise we can get into
# really tricky problems where the CPython interpreter dead locks
@@ -58,9 +82,16 @@ class CallbackFileWrapper(object):
# and allows the garbage collector to do it's thing normally.
self.__callback = None
+ # Closing the temporary file releases memory and frees disk space.
+ # Important when caching big files.
+ self.__buf.close()
+
def read(self, amt=None):
data = self.__fp.read(amt)
- self.__buf.write(data)
+ if data:
+ # We may be dealing with b'', a sign that things are over:
+ # it's passed e.g. after we've already closed self.__buf.
+ self.__buf.write(data)
if self.__is_fp_closed():
self._close()
diff --git a/src/pip/_vendor/cachecontrol/heuristics.py b/src/pip/_vendor/cachecontrol/heuristics.py
index 6c0e9790d..ebe4a96f5 100644
--- a/src/pip/_vendor/cachecontrol/heuristics.py
+++ b/src/pip/_vendor/cachecontrol/heuristics.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
import calendar
import time
diff --git a/src/pip/_vendor/cachecontrol/serialize.py b/src/pip/_vendor/cachecontrol/serialize.py
index 3b6ec2de1..7fe1a3e33 100644
--- a/src/pip/_vendor/cachecontrol/serialize.py
+++ b/src/pip/_vendor/cachecontrol/serialize.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
import base64
import io
import json
@@ -17,24 +21,18 @@ def _b64_decode_str(s):
return _b64_decode_bytes(s).decode("utf8")
-class Serializer(object):
+_default_body_read = object()
+
+class Serializer(object):
def dumps(self, request, response, body=None):
response_headers = CaseInsensitiveDict(response.headers)
if body is None:
+ # When a body isn't passed in, we'll read the response. We
+ # also update the response with a new file handler to be
+ # sure it acts as though it was never read.
body = response.read(decode_content=False)
-
- # NOTE: 99% sure this is dead code. I'm only leaving it
- # here b/c I don't have a test yet to prove
- # it. Basically, before using
- # `cachecontrol.filewrapper.CallbackFileWrapper`,
- # this made an effort to reset the file handle. The
- # `CallbackFileWrapper` short circuits this code by
- # setting the body as the content is consumed, the
- # result being a `body` argument is *always* passed
- # into cache_response, and in turn,
- # `Serializer.dump`.
response._fp = io.BytesIO(body)
# NOTE: This is all a bit weird, but it's really important that on
@@ -46,7 +44,7 @@ class Serializer(object):
# enough to have msgpack know the difference.
data = {
u"response": {
- u"body": body,
+ u"body": body, # Empty bytestring if body is stored separately
u"headers": dict(
(text_type(k), text_type(v)) for k, v in response.headers.items()
),
@@ -71,7 +69,7 @@ class Serializer(object):
return b",".join([b"cc=4", msgpack.dumps(data, use_bin_type=True)])
- def loads(self, request, data):
+ def loads(self, request, data, body_file=None):
# Short circuit if we've been given an empty set of data
if not data:
return
@@ -94,14 +92,14 @@ class Serializer(object):
# Dispatch to the actual load method for the given version
try:
- return getattr(self, "_loads_v{}".format(ver))(request, data)
+ return getattr(self, "_loads_v{}".format(ver))(request, data, body_file)
except AttributeError:
# This is a version we don't have a loads function for, so we'll
# just treat it as a miss and return None
return
- def prepare_response(self, request, cached):
+ def prepare_response(self, request, cached, body_file=None):
"""Verify our vary headers match and construct a real urllib3
HTTPResponse object.
"""
@@ -127,7 +125,10 @@ class Serializer(object):
cached["response"]["headers"] = headers
try:
- body = io.BytesIO(body_raw)
+ if body_file is None:
+ body = io.BytesIO(body_raw)
+ else:
+ body = body_file
except TypeError:
# This can happen if cachecontrol serialized to v1 format (pickle)
# using Python 2. A Python 2 str(byte string) will be unpickled as
@@ -139,21 +140,22 @@ class Serializer(object):
return HTTPResponse(body=body, preload_content=False, **cached["response"])
- def _loads_v0(self, request, data):
+ def _loads_v0(self, request, data, body_file=None):
# The original legacy cache data. This doesn't contain enough
# information to construct everything we need, so we'll treat this as
# a miss.
return
- def _loads_v1(self, request, data):
+ def _loads_v1(self, request, data, body_file=None):
try:
cached = pickle.loads(data)
except ValueError:
return
- return self.prepare_response(request, cached)
+ return self.prepare_response(request, cached, body_file)
- def _loads_v2(self, request, data):
+ def _loads_v2(self, request, data, body_file=None):
+ assert body_file is None
try:
cached = json.loads(zlib.decompress(data).decode("utf8"))
except (ValueError, zlib.error):
@@ -171,18 +173,18 @@ class Serializer(object):
for k, v in cached["vary"].items()
)
- return self.prepare_response(request, cached)
+ return self.prepare_response(request, cached, body_file)
- def _loads_v3(self, request, data):
+ def _loads_v3(self, request, data, body_file):
# Due to Python 2 encoding issues, it's impossible to know for sure
# exactly how to load v3 entries, thus we'll treat these as a miss so
# that they get rewritten out as v4 entries.
return
- def _loads_v4(self, request, data):
+ def _loads_v4(self, request, data, body_file=None):
try:
cached = msgpack.loads(data, raw=False)
except ValueError:
return
- return self.prepare_response(request, cached)
+ return self.prepare_response(request, cached, body_file)
diff --git a/src/pip/_vendor/cachecontrol/wrapper.py b/src/pip/_vendor/cachecontrol/wrapper.py
index d8e6fc6a9..b6ee7f203 100644
--- a/src/pip/_vendor/cachecontrol/wrapper.py
+++ b/src/pip/_vendor/cachecontrol/wrapper.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
from .adapter import CacheControlAdapter
from .cache import DictCache
diff --git a/src/pip/_vendor/certifi.pyi b/src/pip/_vendor/certifi.pyi
deleted file mode 100644
index e5c4d3d2a..000000000
--- a/src/pip/_vendor/certifi.pyi
+++ /dev/null
@@ -1 +0,0 @@
-from certifi import * \ No newline at end of file
diff --git a/src/pip/_vendor/certifi/LICENSE b/src/pip/_vendor/certifi/LICENSE
index c2fda9a26..0a64774ea 100644
--- a/src/pip/_vendor/certifi/LICENSE
+++ b/src/pip/_vendor/certifi/LICENSE
@@ -6,7 +6,7 @@ Certificate data from Mozilla as of: Thu Nov 3 19:04:19 2011#
This is a bundle of X.509 certificates of public Certificate Authorities
(CA). These were automatically extracted from Mozilla's root certificates
file (certdata.txt). This file can be found in the mozilla source tree:
-http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1#
+https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins/certdata.txt
It contains the certificates in PEM format and therefore
can be directly used with curl / libcurl / php_curl, or with
an Apache+mod_ssl webserver for SSL client authentication.
diff --git a/src/pip/_vendor/certifi/__init__.py b/src/pip/_vendor/certifi/__init__.py
index eebdf8886..a3546f125 100644
--- a/src/pip/_vendor/certifi/__init__.py
+++ b/src/pip/_vendor/certifi/__init__.py
@@ -1,3 +1,4 @@
from .core import contents, where
-__version__ = "2021.05.30"
+__all__ = ["contents", "where"]
+__version__ = "2022.12.07"
diff --git a/src/pip/_vendor/certifi/cacert.pem b/src/pip/_vendor/certifi/cacert.pem
index 96e2fc65a..df9e4e3c7 100644
--- a/src/pip/_vendor/certifi/cacert.pem
+++ b/src/pip/_vendor/certifi/cacert.pem
@@ -28,36 +28,6 @@ DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
-----END CERTIFICATE-----
-# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2
-# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2
-# Label: "GlobalSign Root CA - R2"
-# Serial: 4835703278459682885658125
-# MD5 Fingerprint: 94:14:77:7e:3e:5e:fd:8f:30:bd:41:b0:cf:e7:d0:30
-# SHA1 Fingerprint: 75:e0:ab:b6:13:85:12:27:1c:04:f8:5f:dd:de:38:e4:b7:24:2e:fe
-# SHA256 Fingerprint: ca:42:dd:41:74:5f:d0:b8:1e:b9:02:36:2c:f9:d8:bf:71:9d:a1:bd:1b:1e:fc:94:6f:5b:4c:99:f4:2c:1b:9e
------BEGIN CERTIFICATE-----
-MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
-A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
-Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
-MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
-A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
-hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
-v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
-eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
-tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
-C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
-zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
-mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
-V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
-bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
-3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
-J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
-291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
-ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
-AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
-TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
------END CERTIFICATE-----
-
# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
# Label: "Entrust.net Premium 2048 Secure Server CA"
@@ -491,34 +461,6 @@ vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----
-# Issuer: CN=DST Root CA X3 O=Digital Signature Trust Co.
-# Subject: CN=DST Root CA X3 O=Digital Signature Trust Co.
-# Label: "DST Root CA X3"
-# Serial: 91299735575339953335919266965803778155
-# MD5 Fingerprint: 41:03:52:dc:0f:f7:50:1b:16:f0:02:8e:ba:6f:45:c5
-# SHA1 Fingerprint: da:c9:02:4f:54:d8:f6:df:94:93:5f:b1:73:26:38:ca:6a:d7:7c:13
-# SHA256 Fingerprint: 06:87:26:03:31:a7:24:03:d9:09:f1:05:e6:9b:cf:0d:32:e1:bd:24:93:ff:c6:d9:20:6d:11:bc:d6:77:07:39
------BEGIN CERTIFICATE-----
-MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
-MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
-DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
-PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
-Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
-AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
-rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
-OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
-xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
-7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
-aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
-HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
-SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
-ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
-AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
-R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
-JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
-Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
------END CERTIFICATE-----
-
# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG
# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG
# Label: "SwissSign Gold CA - G2"
@@ -694,37 +636,6 @@ BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB
ZQ==
-----END CERTIFICATE-----
-# Issuer: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C.
-# Subject: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C.
-# Label: "Network Solutions Certificate Authority"
-# Serial: 116697915152937497490437556386812487904
-# MD5 Fingerprint: d3:f3:a6:16:c0:fa:6b:1d:59:b1:2d:96:4d:0e:11:2e
-# SHA1 Fingerprint: 74:f8:a3:c3:ef:e7:b3:90:06:4b:83:90:3c:21:64:60:20:e5:df:ce
-# SHA256 Fingerprint: 15:f0:ba:00:a3:ac:7a:f3:ac:88:4c:07:2b:10:11:a0:77:bd:77:c0:97:f4:01:64:b2:f8:59:8a:bd:83:86:0c
------BEGIN CERTIFICATE-----
-MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi
-MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
-MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp
-dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV
-UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO
-ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG
-SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz
-c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP
-OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl
-mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF
-BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4
-qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw
-gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB
-BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu
-bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp
-dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8
-6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/
-h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH
-/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
-wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN
-pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
------END CERTIFICATE-----
-
# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited
# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited
# Label: "COMODO ECC Certification Authority"
@@ -779,36 +690,6 @@ t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
-----END CERTIFICATE-----
-# Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc
-# Subject: CN=Cybertrust Global Root O=Cybertrust, Inc
-# Label: "Cybertrust Global Root"
-# Serial: 4835703278459682877484360
-# MD5 Fingerprint: 72:e4:4a:87:e3:69:40:80:77:ea:bc:e3:f4:ff:f0:e1
-# SHA1 Fingerprint: 5f:43:e5:b1:bf:f8:78:8c:ac:1c:c7:ca:4a:9a:c6:22:2b:cc:34:c6
-# SHA256 Fingerprint: 96:0a:df:00:63:e9:63:56:75:0c:29:65:dd:0a:08:67:da:0b:9c:bd:6e:77:71:4a:ea:fb:23:49:ab:39:3d:a3
------BEGIN CERTIFICATE-----
-MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG
-A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh
-bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE
-ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS
-b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5
-7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS
-J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y
-HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP
-t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz
-FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY
-XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/
-MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw
-hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js
-MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA
-A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj
-Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx
-XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o
-omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc
-A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
-WL1WMRJOEcgh4LMRkWXbtKaIOM5V
------END CERTIFICATE-----
-
# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority
# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority
# Label: "ePKI Root Certification Authority"
@@ -1411,78 +1292,6 @@ t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy
SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
-----END CERTIFICATE-----
-# Issuer: CN=EC-ACC O=Agencia Catalana de Certificacio (NIF Q-0801176-I) OU=Serveis Publics de Certificacio/Vegeu https://www.catcert.net/verarrel (c)03/Jerarquia Entitats de Certificacio Catalanes
-# Subject: CN=EC-ACC O=Agencia Catalana de Certificacio (NIF Q-0801176-I) OU=Serveis Publics de Certificacio/Vegeu https://www.catcert.net/verarrel (c)03/Jerarquia Entitats de Certificacio Catalanes
-# Label: "EC-ACC"
-# Serial: -23701579247955709139626555126524820479
-# MD5 Fingerprint: eb:f5:9d:29:0d:61:f9:42:1f:7c:c2:ba:6d:e3:15:09
-# SHA1 Fingerprint: 28:90:3a:63:5b:52:80:fa:e6:77:4c:0b:6d:a7:d6:ba:a6:4a:f2:e8
-# SHA256 Fingerprint: 88:49:7f:01:60:2f:31:54:24:6a:e2:8c:4d:5a:ef:10:f1:d8:7e:bb:76:62:6f:4a:e0:b7:f9:5b:a7:96:87:99
------BEGIN CERTIFICATE-----
-MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB
-8zELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2Vy
-dGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1
-YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3
-dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UECxMsSmVyYXJxdWlh
-IEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMTBkVD
-LUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQG
-EwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8g
-KE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBD
-ZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQu
-bmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMg
-ZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUNDMIIBIjAN
-BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R
-85iKw5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm
-4CgPukLjbo73FCeTae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaV
-HMf5NLWUhdWZXqBIoH7nF2W4onW4HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNd
-QlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0aE9jD2z3Il3rucO2n5nzbcc8t
-lGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw0JDnJwIDAQAB
-o4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E
-BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4
-opvpXY0wfwYDVR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBo
-dHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidW
-ZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAwDQYJKoZIhvcN
-AQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJlF7W2u++AVtd0x7Y
-/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNaAl6k
-SBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhy
-Rp/7SNVel+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOS
-Agu+TGbrIP65y7WZf+a2E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xl
-nJ2lYJU6Un/10asIbvPuW/mIPX64b24D5EI=
------END CERTIFICATE-----
-
-# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority
-# Subject: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority
-# Label: "Hellenic Academic and Research Institutions RootCA 2011"
-# Serial: 0
-# MD5 Fingerprint: 73:9f:4c:4b:73:5b:79:e9:fa:ba:1c:ef:6e:cb:d5:c9
-# SHA1 Fingerprint: fe:45:65:9b:79:03:5b:98:a1:61:b5:51:2e:ac:da:58:09:48:22:4d
-# SHA256 Fingerprint: bc:10:4f:15:a4:8b:e7:09:dc:a5:42:a7:e1:d4:b9:df:6f:05:45:27:e8:02:ea:a9:2d:59:54:44:25:8a:fe:71
------BEGIN CERTIFICATE-----
-MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix
-RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1
-dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p
-YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw
-NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK
-EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl
-cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
-c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB
-BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz
-dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ
-fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns
-bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD
-75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP
-FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV
-HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp
-5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu
-b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA
-A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p
-6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
-TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7
-dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys
-Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI
-l7WdmplNsDz4SgCbZN2fOUvRJ9e4
------END CERTIFICATE-----
-
# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967
# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967
# Label: "Actalis Authentication Root CA"
@@ -2342,27 +2151,6 @@ zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW
RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
-----END CERTIFICATE-----
-# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4
-# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4
-# Label: "GlobalSign ECC Root CA - R4"
-# Serial: 14367148294922964480859022125800977897474
-# MD5 Fingerprint: 20:f0:27:68:d1:7e:a0:9d:0e:e6:2a:ca:df:5c:89:8e
-# SHA1 Fingerprint: 69:69:56:2e:40:80:f4:24:a1:e7:19:9f:14:ba:f3:ee:58:ab:6a:bb
-# SHA256 Fingerprint: be:c9:49:11:c2:95:56:76:db:6c:0a:55:09:86:d7:6e:3b:a0:05:66:7c:44:2c:97:62:b4:fb:b7:73:de:22:8c
------BEGIN CERTIFICATE-----
-MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk
-MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH
-bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
-DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
-QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
-MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ
-FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw
-DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F
-uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX
-kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs
-ewv4n4Q=
------END CERTIFICATE-----
-
# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5
# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5
# Label: "GlobalSign ECC Root CA - R5"
@@ -2385,46 +2173,6 @@ KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg
xwy8p2Fp8fc74SrL+SvzZpA3
-----END CERTIFICATE-----
-# Issuer: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden
-# Subject: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden
-# Label: "Staat der Nederlanden EV Root CA"
-# Serial: 10000013
-# MD5 Fingerprint: fc:06:af:7b:e8:1a:f1:9a:b4:e8:d2:70:1f:c0:f5:ba
-# SHA1 Fingerprint: 76:e2:7e:c1:4f:db:82:c1:c0:a6:75:b5:05:be:3d:29:b4:ed:db:bb
-# SHA256 Fingerprint: 4d:24:91:41:4c:fe:95:67:46:ec:4c:ef:a6:cf:6f:72:e2:8a:13:29:43:2f:9d:8a:90:7a:c4:cb:5d:ad:c1:5a
------BEGIN CERTIFICATE-----
-MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO
-TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh
-dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y
-MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg
-TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS
-b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS
-M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC
-UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d
-Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p
-rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l
-pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb
-j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC
-KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS
-/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X
-cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH
-1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP
-px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB
-/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7
-MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI
-eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u
-2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS
-v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC
-wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy
-CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e
-vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6
-Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa
-Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL
-eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8
-FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc
-7uzXLg==
------END CERTIFICATE-----
-
# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust
# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust
# Label: "IdenTrust Commercial Root CA 1"
@@ -3032,116 +2780,6 @@ T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe
MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g==
-----END CERTIFICATE-----
-# Issuer: CN=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority
-# Subject: CN=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority
-# Label: "TrustCor RootCert CA-1"
-# Serial: 15752444095811006489
-# MD5 Fingerprint: 6e:85:f1:dc:1a:00:d3:22:d5:b2:b2:ac:6b:37:05:45
-# SHA1 Fingerprint: ff:bd:cd:e7:82:c8:43:5e:3c:6f:26:86:5c:ca:a8:3a:45:5b:c3:0a
-# SHA256 Fingerprint: d4:0e:9c:86:cd:8f:e4:68:c1:77:69:59:f4:9e:a7:74:fa:54:86:84:b6:c4:06:f3:90:92:61:f4:dc:e2:57:5c
------BEGIN CERTIFICATE-----
-MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYD
-VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk
-MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U
-cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29y
-IFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkxMjMxMTcyMzE2WjCB
-pDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFuYW1h
-IENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUG
-A1UECwweVHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZU
-cnVzdENvciBSb290Q2VydCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
-CgKCAQEAv463leLCJhJrMxnHQFgKq1mqjQCj/IDHUHuO1CAmujIS2CNUSSUQIpid
-RtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4pQa81QBeCQryJ3pS/C3V
-seq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0JEsq1pme
-9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CV
-EY4hgLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorW
-hnAbJN7+KIor0Gqw/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/
-DeOxCbeKyKsZn3MzUOcwHwYDVR0jBBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcw
-DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD
-ggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5mDo4Nvu7Zp5I
-/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf
-ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZ
-yonnMlo2HD6CqFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djts
-L1Ac59v2Z3kf9YKVmgenFK+P3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdN
-zl/HHk484IkzlQsPpTLWPFp5LBk=
------END CERTIFICATE-----
-
-# Issuer: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority
-# Subject: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority
-# Label: "TrustCor RootCert CA-2"
-# Serial: 2711694510199101698
-# MD5 Fingerprint: a2:e1:f8:18:0b:ba:45:d5:c7:41:2a:bb:37:52:45:64
-# SHA1 Fingerprint: b8:be:6d:cb:56:f1:55:b9:63:d4:12:ca:4e:06:34:c7:94:b2:1c:c0
-# SHA256 Fingerprint: 07:53:e9:40:37:8c:1b:d5:e3:83:6e:39:5d:ae:a5:cb:83:9e:50:46:f1:bd:0e:ae:19:51:cf:10:fe:c7:c9:65
------BEGIN CERTIFICATE-----
-MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNV
-BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw
-IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy
-dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEfMB0GA1UEAwwWVHJ1c3RDb3Ig
-Um9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEyMzExNzI2MzlaMIGk
-MQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEg
-Q2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYD
-VQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRy
-dXN0Q29yIFJvb3RDZXJ0IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
-AoICAQCnIG7CKqJiJJWQdsg4foDSq8GbZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo+
-QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9NkRvRUqdw6VC0xK5mC8tkq
-1+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1oYxOdqHp
-2yqlO/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nK
-DOObXUm4TOJXsZiKQlecdu/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hape
-az6LMvYHL1cEksr1/p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF
-3wP+TfSvPd9cW436cOGlfifHhi5qjxLGhF5DUVCcGZt45vz27Ud+ez1m7xMTiF88
-oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQPeSghYA2FFn3XVDjxklb9tTNM
-g9zXEJ9L/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+CtgrKAmrhQhJ8Z3
-mjOAPF5GP/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh
-8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAd
-BgNVHQ4EFgQU2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6U
-nrybPZx9mCAZ5YwwYrIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYw
-DQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/hOsh80QA9z+LqBrWyOrsGS2h60COX
-dKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnpkpfbsEZC89NiqpX+
-MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv2wnL
-/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RX
-CI/hOWB3S6xZhBqI8d3LT3jX5+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYa
-ZH9bDTMJBzN7Bj8RpFxwPIXAz+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW
-2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dvDDqPys/cA8GiCcjl/YBeyGBCARsaU1q7
-N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYURpFHmygk71dSTlxCnKr3
-Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs/hCBcNANExdqtvArB
-As8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp
-5KeXRKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu
-1uwJ
------END CERTIFICATE-----
-
-# Issuer: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority
-# Subject: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority
-# Label: "TrustCor ECA-1"
-# Serial: 9548242946988625984
-# MD5 Fingerprint: 27:92:23:1d:0a:f5:40:7c:e9:e6:6b:9d:d8:f5:e7:6c
-# SHA1 Fingerprint: 58:d1:df:95:95:67:6b:63:c0:f0:5b:1c:17:4d:8b:84:0b:c8:78:bd
-# SHA256 Fingerprint: 5a:88:5d:b1:9c:01:d9:12:c5:75:93:88:93:8c:af:bb:df:03:1a:b2:d4:8e:91:ee:15:58:9b:42:97:1d:03:9c
------BEGIN CERTIFICATE-----
-MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYD
-VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk
-MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U
-cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFzAVBgNVBAMMDlRydXN0Q29y
-IEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3MjgwN1owgZwxCzAJBgNV
-BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw
-IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy
-dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3Ig
-RUNBLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb
-3w9U73NjKYKtR8aja+3+XzP4Q1HpGjORMRegdMTUpwHmspI+ap3tDvl0mEDTPwOA
-BoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23xFUfJ3zSCNV2HykVh0A5
-3ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmcp0yJF4Ou
-owReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/
-wZ0+fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZF
-ZtS6mFjBAgMBAAGjYzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAf
-BgNVHSMEGDAWgBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/
-MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEABT41XBVwm8nHc2Fv
-civUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u/ukZMjgDfxT2
-AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F
-hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50
-soIipX1TH0XsJ5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BI
-WJZpTdwHjFGTot+fDz2LYLSCjaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1Wi
-tJ/X5g==
------END CERTIFICATE-----
-
# Issuer: CN=SSL.com Root Certification Authority RSA O=SSL Corporation
# Subject: CN=SSL.com Root Certification Authority RSA O=SSL Corporation
# Label: "SSL.com Root Certification Authority RSA"
@@ -3337,126 +2975,6 @@ rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV
Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
-----END CERTIFICATE-----
-# Issuer: CN=GTS Root R1 O=Google Trust Services LLC
-# Subject: CN=GTS Root R1 O=Google Trust Services LLC
-# Label: "GTS Root R1"
-# Serial: 146587175971765017618439757810265552097
-# MD5 Fingerprint: 82:1a:ef:d4:d2:4a:f2:9f:e2:3d:97:06:14:70:72:85
-# SHA1 Fingerprint: e1:c9:50:e6:ef:22:f8:4c:56:45:72:8b:92:20:60:d7:d5:a7:a3:e8
-# SHA256 Fingerprint: 2a:57:54:71:e3:13:40:bc:21:58:1c:bd:2c:f1:3e:15:84:63:20:3e:ce:94:bc:f9:d3:cc:19:6b:f0:9a:54:72
------BEGIN CERTIFICATE-----
-MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH
-MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
-QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
-MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
-cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB
-AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM
-f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX
-mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7
-zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P
-fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc
-vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4
-Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp
-zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO
-Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW
-k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+
-DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF
-lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
-HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW
-Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1
-d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z
-XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR
-gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3
-d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv
-J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg
-DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM
-+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy
-F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9
-SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws
-E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl
------END CERTIFICATE-----
-
-# Issuer: CN=GTS Root R2 O=Google Trust Services LLC
-# Subject: CN=GTS Root R2 O=Google Trust Services LLC
-# Label: "GTS Root R2"
-# Serial: 146587176055767053814479386953112547951
-# MD5 Fingerprint: 44:ed:9a:0e:a4:09:3b:00:f2:ae:4c:a3:c6:61:b0:8b
-# SHA1 Fingerprint: d2:73:96:2a:2a:5e:39:9f:73:3f:e1:c7:1e:64:3f:03:38:34:fc:4d
-# SHA256 Fingerprint: c4:5d:7b:b0:8e:6d:67:e6:2e:42:35:11:0b:56:4e:5f:78:fd:92:ef:05:8c:84:0a:ea:4e:64:55:d7:58:5c:60
------BEGIN CERTIFICATE-----
-MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH
-MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
-QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
-MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
-cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB
-AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv
-CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg
-GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu
-XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd
-re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu
-PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1
-mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K
-8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj
-x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR
-nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0
-kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok
-twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
-HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp
-8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT
-vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT
-z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA
-pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb
-pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB
-R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R
-RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk
-0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC
-5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF
-izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn
-yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC
------END CERTIFICATE-----
-
-# Issuer: CN=GTS Root R3 O=Google Trust Services LLC
-# Subject: CN=GTS Root R3 O=Google Trust Services LLC
-# Label: "GTS Root R3"
-# Serial: 146587176140553309517047991083707763997
-# MD5 Fingerprint: 1a:79:5b:6b:04:52:9c:5d:c7:74:33:1b:25:9a:f9:25
-# SHA1 Fingerprint: 30:d4:24:6f:07:ff:db:91:89:8a:0b:e9:49:66:11:eb:8c:5e:46:e5
-# SHA256 Fingerprint: 15:d5:b8:77:46:19:ea:7d:54:ce:1c:a6:d0:b0:c4:03:e0:37:a9:17:f1:31:e8:a0:4e:1e:6b:7a:71:ba:bc:e5
------BEGIN CERTIFICATE-----
-MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw
-CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
-MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
-MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
-Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA
-IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout
-736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A
-DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
-DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk
-fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA
-njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd
------END CERTIFICATE-----
-
-# Issuer: CN=GTS Root R4 O=Google Trust Services LLC
-# Subject: CN=GTS Root R4 O=Google Trust Services LLC
-# Label: "GTS Root R4"
-# Serial: 146587176229350439916519468929765261721
-# MD5 Fingerprint: 5d:b6:6a:c4:60:17:24:6a:1a:99:a8:4b:ee:5e:b4:26
-# SHA1 Fingerprint: 2a:1d:60:27:d9:4a:b1:0a:1c:4d:91:5c:cd:33:a0:cb:3e:2d:54:cb
-# SHA256 Fingerprint: 71:cc:a5:39:1f:9e:79:4b:04:80:25:30:b3:63:e1:21:da:8a:30:43:bb:26:66:2f:ea:4d:ca:7f:c9:51:a4:bd
------BEGIN CERTIFICATE-----
-MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw
-CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
-MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
-MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
-Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA
-IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu
-hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l
-xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
-DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0
-CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx
-sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w==
------END CERTIFICATE-----
-
# Issuer: CN=UCA Global G2 Root O=UniTrust
# Subject: CN=UCA Global G2 Root O=UniTrust
# Label: "UCA Global G2 Root"
@@ -4255,3 +3773,755 @@ qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP
0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf
E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb
-----END CERTIFICATE-----
+
+# Issuer: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique
+# Subject: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique
+# Label: "TunTrust Root CA"
+# Serial: 108534058042236574382096126452369648152337120275
+# MD5 Fingerprint: 85:13:b9:90:5b:36:5c:b6:5e:b8:5a:f8:e0:31:57:b4
+# SHA1 Fingerprint: cf:e9:70:84:0f:e0:73:0f:9d:f6:0c:7f:2c:4b:ee:20:46:34:9c:bb
+# SHA256 Fingerprint: 2e:44:10:2a:b5:8c:b8:54:19:45:1c:8e:19:d9:ac:f3:66:2c:af:bc:61:4b:6a:53:96:0a:30:f7:d0:e2:eb:41
+-----BEGIN CERTIFICATE-----
+MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL
+BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg
+Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv
+b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG
+EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u
+IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ
+n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd
+2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF
+VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ
+GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF
+li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU
+r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2
+eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb
+MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg
+jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB
+7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW
+5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE
+ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0
+90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z
+xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu
+QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4
+FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH
+22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP
+xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn
+dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5
+Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b
+nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ
+CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH
+u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj
+d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=
+-----END CERTIFICATE-----
+
+# Issuer: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Subject: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Label: "HARICA TLS RSA Root CA 2021"
+# Serial: 76817823531813593706434026085292783742
+# MD5 Fingerprint: 65:47:9b:58:86:dd:2c:f0:fc:a2:84:1f:1e:96:c4:91
+# SHA1 Fingerprint: 02:2d:05:82:fa:88:ce:14:0c:06:79:de:7f:14:10:e9:45:d7:a5:6d
+# SHA256 Fingerprint: d9:5d:0e:8e:da:79:52:5b:f9:be:b1:1b:14:d2:10:0d:32:94:98:5f:0c:62:d9:fa:bd:9c:d9:99:ec:cb:7b:1d
+-----BEGIN CERTIFICATE-----
+MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs
+MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
+c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg
+Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL
+MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl
+YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv
+b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l
+mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE
+4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv
+a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M
+pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw
+Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b
+LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY
+AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB
+AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq
+E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr
+W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ
+CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE
+AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU
+X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3
+f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja
+H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP
+JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P
+zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt
+jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0
+/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT
+BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79
+aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW
+xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU
+63ZTGI0RmLo=
+-----END CERTIFICATE-----
+
+# Issuer: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Subject: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Label: "HARICA TLS ECC Root CA 2021"
+# Serial: 137515985548005187474074462014555733966
+# MD5 Fingerprint: ae:f7:4c:e5:66:35:d1:b7:9b:8c:22:93:74:d3:4b:b0
+# SHA1 Fingerprint: bc:b0:c1:9d:e9:98:92:70:19:38:57:e9:8d:a7:b4:5d:6e:ee:01:48
+# SHA256 Fingerprint: 3f:99:cc:47:4a:cf:ce:4d:fe:d5:87:94:66:5e:47:8d:15:47:73:9f:2e:78:0f:1b:b4:ca:9b:13:30:97:d4:01
+-----BEGIN CERTIFICATE-----
+MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw
+CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh
+cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v
+dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG
+A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj
+aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg
+Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7
+KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y
+STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD
+AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw
+SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN
+nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps
+-----END CERTIFICATE-----
+
+# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068
+# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068
+# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068"
+# Serial: 1977337328857672817
+# MD5 Fingerprint: 4e:6e:9b:54:4c:ca:b7:fa:48:e4:90:b1:15:4b:1c:a3
+# SHA1 Fingerprint: 0b:be:c2:27:22:49:cb:39:aa:db:35:5c:53:e3:8c:ae:78:ff:b6:fe
+# SHA256 Fingerprint: 57:de:05:83:ef:d2:b2:6e:03:61:da:99:da:9d:f4:64:8d:ef:7e:e8:44:1c:3b:72:8a:fa:9b:cd:e0:f9:b2:6a
+-----BEGIN CERTIFICATE-----
+MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE
+BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
+cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1
+MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
+Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9
+thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM
+cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG
+L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i
+NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h
+X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b
+m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy
+Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja
+EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T
+KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF
+6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh
+OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc
+tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd
+IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j
+b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC
+AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw
+ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m
+iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF
+Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ
+hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P
+Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE
+EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV
+1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t
+CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR
+5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw
+f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9
+ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK
+GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV
+-----END CERTIFICATE-----
+
+# Issuer: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd.
+# Subject: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd.
+# Label: "vTrus ECC Root CA"
+# Serial: 630369271402956006249506845124680065938238527194
+# MD5 Fingerprint: de:4b:c1:f5:52:8c:9b:43:e1:3e:8f:55:54:17:8d:85
+# SHA1 Fingerprint: f6:9c:db:b0:fc:f6:02:13:b6:52:32:a6:a3:91:3f:16:70:da:c3:e1
+# SHA256 Fingerprint: 30:fb:ba:2c:32:23:8e:2a:98:54:7a:f9:79:31:e5:50:42:8b:9b:3f:1c:8e:eb:66:33:dc:fa:86:c5:b2:7d:d3
+-----BEGIN CERTIFICATE-----
+MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMw
+RzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAY
+BgNVBAMTEXZUcnVzIEVDQyBSb290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDcz
+MTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28u
+LEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYwEAYHKoZIzj0CAQYF
+K4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+cToL0
+v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUd
+e4BdS49nTPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYD
+VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIw
+V53dVvHH4+m4SVBrm2nDb+zDfSXkV5UTQJtS0zvzQBm8JsctBp61ezaf9SXUY2sA
+AjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQLYgmRWAD5Tfs0aNoJrSEG
+GJTO
+-----END CERTIFICATE-----
+
+# Issuer: CN=vTrus Root CA O=iTrusChina Co.,Ltd.
+# Subject: CN=vTrus Root CA O=iTrusChina Co.,Ltd.
+# Label: "vTrus Root CA"
+# Serial: 387574501246983434957692974888460947164905180485
+# MD5 Fingerprint: b8:c9:37:df:fa:6b:31:84:64:c5:ea:11:6a:1b:75:fc
+# SHA1 Fingerprint: 84:1a:69:fb:f5:cd:1a:25:34:13:3d:e3:f8:fc:b8:99:d0:c9:14:b7
+# SHA256 Fingerprint: 8a:71:de:65:59:33:6f:42:6c:26:e5:38:80:d0:0d:88:a1:8d:a4:c6:a9:1f:0d:cb:61:94:e2:06:c5:c9:63:87
+-----BEGIN CERTIFICATE-----
+MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQEL
+BQAwQzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4x
+FjAUBgNVBAMTDXZUcnVzIFJvb3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMx
+MDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoGA1UEChMTaVRydXNDaGluYSBDby4s
+THRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZotsSKYc
+IrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykU
+AyyNJJrIZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+
+GrPSbcKvdmaVayqwlHeFXgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z9
+8Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KAYPxMvDVTAWqXcoKv8R1w6Jz1717CbMdH
+flqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70kLJrxLT5ZOrpGgrIDajt
+J8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2AXPKBlim
+0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZN
+pGvu/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQ
+UqqzApVg+QxMaPnu1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHW
+OXSuTEGC2/KmSNGzm/MzqvOmwMVO9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMB
+AAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYgscasGrz2iTAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAKbqSSaet
+8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd
+nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1j
+bhd47F18iMjrjld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvM
+Kar5CKXiNxTKsbhm7xqC5PD48acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIiv
+TDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJnxDHO2zTlJQNgJXtxmOTAGytfdELS
+S8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554WgicEFOwE30z9J4nfr
+I8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4sEb9
+b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNB
+UvupLnKWnyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1P
+Ti07NEPhmg4NpGaXutIcSkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929ven
+sBxXVsFy6K2ir40zSbofitzmdHxghm+Hl3s=
+-----END CERTIFICATE-----
+
+# Issuer: CN=ISRG Root X2 O=Internet Security Research Group
+# Subject: CN=ISRG Root X2 O=Internet Security Research Group
+# Label: "ISRG Root X2"
+# Serial: 87493402998870891108772069816698636114
+# MD5 Fingerprint: d3:9e:c4:1e:23:3c:a6:df:cf:a3:7e:6d:e0:14:e6:e5
+# SHA1 Fingerprint: bd:b1:b9:3c:d5:97:8d:45:c6:26:14:55:f8:db:95:c7:5a:d1:53:af
+# SHA256 Fingerprint: 69:72:9b:8e:15:a8:6e:fc:17:7a:57:af:b7:17:1d:fc:64:ad:d2:8c:2f:ca:8c:f1:50:7e:34:45:3c:cb:14:70
+-----BEGIN CERTIFICATE-----
+MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
+CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
+R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
+MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
+ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
+EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
+ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
+zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
+tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
+/q4AaOeMSQ+2b1tbFfLn
+-----END CERTIFICATE-----
+
+# Issuer: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd.
+# Subject: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd.
+# Label: "HiPKI Root CA - G1"
+# Serial: 60966262342023497858655262305426234976
+# MD5 Fingerprint: 69:45:df:16:65:4b:e8:68:9a:8f:76:5f:ff:80:9e:d3
+# SHA1 Fingerprint: 6a:92:e4:a8:ee:1b:ec:96:45:37:e3:29:57:49:cd:96:e3:e5:d2:60
+# SHA256 Fingerprint: f0:15:ce:3c:c2:39:bf:ef:06:4b:e9:f1:d2:c4:17:e1:a0:26:4a:0a:94:be:1f:0c:8d:12:18:64:eb:69:49:cc
+-----BEGIN CERTIFICATE-----
+MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP
+MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
+ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa
+Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3
+YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw
+qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv
+Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6
+lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz
+Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ
+KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK
+FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj
+HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr
+y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ
+/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM
+a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6
+fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
+HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG
+SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi
+7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc
+SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza
+ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc
+XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg
+iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho
+L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF
+Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr
+kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+
+vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU
+YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ==
+-----END CERTIFICATE-----
+
+# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4
+# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4
+# Label: "GlobalSign ECC Root CA - R4"
+# Serial: 159662223612894884239637590694
+# MD5 Fingerprint: 26:29:f8:6d:e1:88:bf:a2:65:7f:aa:c4:cd:0f:7f:fc
+# SHA1 Fingerprint: 6b:a0:b0:98:e1:71:ef:5a:ad:fe:48:15:80:77:10:f4:bd:6f:0b:28
+# SHA256 Fingerprint: b0:85:d7:0b:96:4f:19:1a:73:e4:af:0d:54:ae:7a:0e:07:aa:fd:af:9b:71:dd:08:62:13:8a:b7:32:5a:24:a2
+-----BEGIN CERTIFICATE-----
+MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD
+VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh
+bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw
+MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g
+UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT
+BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx
+uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV
+HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/
++wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147
+bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
+-----END CERTIFICATE-----
+
+# Issuer: CN=GTS Root R1 O=Google Trust Services LLC
+# Subject: CN=GTS Root R1 O=Google Trust Services LLC
+# Label: "GTS Root R1"
+# Serial: 159662320309726417404178440727
+# MD5 Fingerprint: 05:fe:d0:bf:71:a8:a3:76:63:da:01:e0:d8:52:dc:40
+# SHA1 Fingerprint: e5:8c:1c:c4:91:3b:38:63:4b:e9:10:6e:e3:ad:8e:6b:9d:d9:81:4a
+# SHA256 Fingerprint: d9:47:43:2a:bd:e7:b7:fa:90:fc:2e:6b:59:10:1b:12:80:e0:e1:c7:e4:e4:0f:a3:c6:88:7f:ff:57:a7:f4:cf
+-----BEGIN CERTIFICATE-----
+MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
+CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
+MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
+MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
+Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo
+27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w
+Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw
+TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl
+qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH
+szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8
+Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk
+MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
+wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p
+aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN
+VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID
+AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb
+C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
+QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy
+h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4
+7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J
+ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef
+MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/
+Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT
+6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ
+0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm
+2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb
+bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
+-----END CERTIFICATE-----
+
+# Issuer: CN=GTS Root R2 O=Google Trust Services LLC
+# Subject: CN=GTS Root R2 O=Google Trust Services LLC
+# Label: "GTS Root R2"
+# Serial: 159662449406622349769042896298
+# MD5 Fingerprint: 1e:39:c0:53:e6:1e:29:82:0b:ca:52:55:36:5d:57:dc
+# SHA1 Fingerprint: 9a:44:49:76:32:db:de:fa:d0:bc:fb:5a:7b:17:bd:9e:56:09:24:94
+# SHA256 Fingerprint: 8d:25:cd:97:22:9d:bf:70:35:6b:da:4e:b3:cc:73:40:31:e2:4c:f0:0f:af:cf:d3:2d:c7:6e:b5:84:1c:7e:a8
+-----BEGIN CERTIFICATE-----
+MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw
+CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
+MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
+MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
+Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt
+nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY
+6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu
+MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k
+RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg
+f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV
++3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo
+dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW
+Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa
+G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq
+gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID
+AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H
+vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8
+0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC
+B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u
+NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg
+yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev
+HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6
+xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR
+TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg
+JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV
+7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl
+6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL
+-----END CERTIFICATE-----
+
+# Issuer: CN=GTS Root R3 O=Google Trust Services LLC
+# Subject: CN=GTS Root R3 O=Google Trust Services LLC
+# Label: "GTS Root R3"
+# Serial: 159662495401136852707857743206
+# MD5 Fingerprint: 3e:e7:9d:58:02:94:46:51:94:e5:e0:22:4a:8b:e7:73
+# SHA1 Fingerprint: ed:e5:71:80:2b:c8:92:b9:5b:83:3c:d2:32:68:3f:09:cd:a0:1e:46
+# SHA256 Fingerprint: 34:d8:a7:3e:e2:08:d9:bc:db:0d:95:65:20:93:4b:4e:40:e6:94:82:59:6e:8b:6f:73:c8:42:6b:01:0a:6f:48
+-----BEGIN CERTIFICATE-----
+MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD
+VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
+A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
+WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
+IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
+AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G
+jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2
+4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7
+VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm
+ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X
+-----END CERTIFICATE-----
+
+# Issuer: CN=GTS Root R4 O=Google Trust Services LLC
+# Subject: CN=GTS Root R4 O=Google Trust Services LLC
+# Label: "GTS Root R4"
+# Serial: 159662532700760215368942768210
+# MD5 Fingerprint: 43:96:83:77:19:4d:76:b3:9d:65:52:e4:1d:22:a5:e8
+# SHA1 Fingerprint: 77:d3:03:67:b5:e0:0c:15:f6:0c:38:61:df:7c:e1:3b:92:46:4d:47
+# SHA256 Fingerprint: 34:9d:fa:40:58:c5:e2:63:12:3b:39:8a:e7:95:57:3c:4e:13:13:c8:3f:e6:8f:93:55:6c:d5:e8:03:1b:3c:7d
+-----BEGIN CERTIFICATE-----
+MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD
+VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
+A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
+WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
+IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
+AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi
+QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR
+HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D
+9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8
+p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD
+-----END CERTIFICATE-----
+
+# Issuer: CN=Telia Root CA v2 O=Telia Finland Oyj
+# Subject: CN=Telia Root CA v2 O=Telia Finland Oyj
+# Label: "Telia Root CA v2"
+# Serial: 7288924052977061235122729490515358
+# MD5 Fingerprint: 0e:8f:ac:aa:82:df:85:b1:f4:dc:10:1c:fc:99:d9:48
+# SHA1 Fingerprint: b9:99:cd:d1:73:50:8a:c4:47:05:08:9c:8c:88:fb:be:a0:2b:40:cd
+# SHA256 Fingerprint: 24:2b:69:74:2f:cb:1e:5b:2a:bf:98:89:8b:94:57:21:87:54:4e:5b:4d:99:11:78:65:73:62:1f:6a:74:b8:2c
+-----BEGIN CERTIFICATE-----
+MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx
+CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE
+AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1
+NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ
+MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq
+AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9
+vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9
+lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD
+n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT
+7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o
+6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC
+TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6
+WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R
+DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI
+pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj
+YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy
+rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
+AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ
+8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi
+0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM
+A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS
+SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K
+TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF
+6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er
+3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt
+Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT
+VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW
+ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA
+rBPuUBQemMc=
+-----END CERTIFICATE-----
+
+# Issuer: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH
+# Subject: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH
+# Label: "D-TRUST BR Root CA 1 2020"
+# Serial: 165870826978392376648679885835942448534
+# MD5 Fingerprint: b5:aa:4b:d5:ed:f7:e3:55:2e:8f:72:0a:f3:75:b8:ed
+# SHA1 Fingerprint: 1f:5b:98:f0:e3:b5:f7:74:3c:ed:e6:b0:36:7d:32:cd:f4:09:41:67
+# SHA256 Fingerprint: e5:9a:aa:81:60:09:c2:2b:ff:5b:25:ba:d3:7d:f3:06:f0:49:79:7c:1f:81:d8:5a:b0:89:e6:57:bd:8f:00:44
+-----BEGIN CERTIFICATE-----
+MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw
+CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS
+VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5
+NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG
+A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB
+BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS
+zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0
+QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/
+VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g
+PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf
+Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l
+dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1
+c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO
+PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW
+wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV
+dWNbFJWcHwHP2NVypw87
+-----END CERTIFICATE-----
+
+# Issuer: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH
+# Subject: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH
+# Label: "D-TRUST EV Root CA 1 2020"
+# Serial: 126288379621884218666039612629459926992
+# MD5 Fingerprint: 8c:2d:9d:70:9f:48:99:11:06:11:fb:e9:cb:30:c0:6e
+# SHA1 Fingerprint: 61:db:8c:21:59:69:03:90:d8:7c:9c:12:86:54:cf:9d:3d:f4:dd:07
+# SHA256 Fingerprint: 08:17:0d:1a:a3:64:53:90:1a:2f:95:92:45:e3:47:db:0c:8d:37:ab:aa:bc:56:b8:1a:a1:00:dc:95:89:70:db
+-----BEGIN CERTIFICATE-----
+MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw
+CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS
+VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5
+NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG
+A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB
+BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC
+/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD
+wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3
+OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g
+PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf
+Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l
+dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1
+c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO
+PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA
+y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb
+gfM0agPnIjhQW+0ZT0MW
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc.
+# Subject: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc.
+# Label: "DigiCert TLS ECC P384 Root G5"
+# Serial: 13129116028163249804115411775095713523
+# MD5 Fingerprint: d3:71:04:6a:43:1c:db:a6:59:e1:a8:a3:aa:c5:71:ed
+# SHA1 Fingerprint: 17:f3:de:5e:9f:0f:19:e9:8e:f6:1f:32:26:6e:20:c4:07:ae:30:ee
+# SHA256 Fingerprint: 01:8e:13:f0:77:25:32:cf:80:9b:d1:b1:72:81:86:72:83:fc:48:c6:e1:3b:e9:c6:98:12:85:4a:49:0c:1b:05
+-----BEGIN CERTIFICATE-----
+MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp
+Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2
+MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
+bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG
+ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS
+7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp
+0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS
+B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49
+BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ
+LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4
+DXZDjC5Ty3zfDBeWUA==
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc.
+# Subject: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc.
+# Label: "DigiCert TLS RSA4096 Root G5"
+# Serial: 11930366277458970227240571539258396554
+# MD5 Fingerprint: ac:fe:f7:34:96:a9:f2:b3:b4:12:4b:e4:27:41:6f:e1
+# SHA1 Fingerprint: a7:88:49:dc:5d:7c:75:8c:8c:de:39:98:56:b3:aa:d0:b2:a5:71:35
+# SHA256 Fingerprint: 37:1a:00:dc:05:33:b3:72:1a:7e:eb:40:e8:41:9e:70:79:9d:2b:0a:0f:2c:1d:80:69:31:65:f7:ce:c4:ad:75
+-----BEGIN CERTIFICATE-----
+MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT
+HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN
+NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs
+IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+
+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0
+2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp
+wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM
+pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD
+nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po
+sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx
+Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd
+Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX
+KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe
+XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL
+tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv
+TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN
+AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw
+GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H
+PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF
+O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ
+REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik
+AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv
+/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+
+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw
+MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF
+qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK
+ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certainly Root R1 O=Certainly
+# Subject: CN=Certainly Root R1 O=Certainly
+# Label: "Certainly Root R1"
+# Serial: 188833316161142517227353805653483829216
+# MD5 Fingerprint: 07:70:d4:3e:82:87:a0:fa:33:36:13:f4:fa:33:e7:12
+# SHA1 Fingerprint: a0:50:ee:0f:28:71:f4:27:b2:12:6d:6f:50:96:25:ba:cc:86:42:af
+# SHA256 Fingerprint: 77:b8:2c:d8:64:4c:43:05:f7:ac:c5:cb:15:6b:45:67:50:04:03:3d:51:c6:0c:62:02:a8:e0:c3:34:67:d3:a0
+-----BEGIN CERTIFICATE-----
+MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw
+PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy
+dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0
+YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2
+1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT
+vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed
+aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0
+1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5
+r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5
+cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ
+wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ
+6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA
+2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH
+Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR
+eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB
+/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u
+d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr
+PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d
+8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi
+1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd
+rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di
+taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7
+lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj
+yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn
+Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy
+yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n
+wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6
+OV+KmalBWQewLK8=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certainly Root E1 O=Certainly
+# Subject: CN=Certainly Root E1 O=Certainly
+# Label: "Certainly Root E1"
+# Serial: 8168531406727139161245376702891150584
+# MD5 Fingerprint: 0a:9e:ca:cd:3e:52:50:c6:36:f3:4b:a3:ed:a7:53:e9
+# SHA1 Fingerprint: f9:e1:6d:dc:01:89:cf:d5:82:45:63:3e:c5:37:7d:c2:eb:93:6f:2b
+# SHA256 Fingerprint: b4:58:5f:22:e4:ac:75:6a:4e:86:12:a1:36:1c:5d:9d:03:1a:93:fd:84:fe:bb:77:8f:a3:06:8b:0f:c4:2d:c2
+-----BEGIN CERTIFICATE-----
+MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw
+CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu
+bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ
+BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s
+eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK
++IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2
+QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4
+hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm
+ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG
+BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR
+-----END CERTIFICATE-----
+
+# Issuer: CN=E-Tugra Global Root CA RSA v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center
+# Subject: CN=E-Tugra Global Root CA RSA v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center
+# Label: "E-Tugra Global Root CA RSA v3"
+# Serial: 75951268308633135324246244059508261641472512052
+# MD5 Fingerprint: 22:be:10:f6:c2:f8:03:88:73:5f:33:29:47:28:47:a4
+# SHA1 Fingerprint: e9:a8:5d:22:14:52:1c:5b:aa:0a:b4:be:24:6a:23:8a:c9:ba:e2:a9
+# SHA256 Fingerprint: ef:66:b0:b1:0a:3c:db:9f:2e:36:48:c7:6b:d2:af:18:ea:d2:bf:e6:f1:17:65:5e:28:c4:06:0d:a1:a3:f4:c2
+-----BEGIN CERTIFICATE-----
+MIIF8zCCA9ugAwIBAgIUDU3FzRYilZYIfrgLfxUGNPt5EDQwDQYJKoZIhvcNAQEL
+BQAwgYAxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUt
+VHVncmEgRUJHIEEuUy4xHTAbBgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYw
+JAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENBIFJTQSB2MzAeFw0yMDAzMTgw
+OTA3MTdaFw00NTAzMTIwOTA3MTdaMIGAMQswCQYDVQQGEwJUUjEPMA0GA1UEBxMG
+QW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1
+Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBD
+QSBSU0EgdjMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCiZvCJt3J7
+7gnJY9LTQ91ew6aEOErxjYG7FL1H6EAX8z3DeEVypi6Q3po61CBxyryfHUuXCscx
+uj7X/iWpKo429NEvx7epXTPcMHD4QGxLsqYxYdE0PD0xesevxKenhOGXpOhL9hd8
+7jwH7eKKV9y2+/hDJVDqJ4GohryPUkqWOmAalrv9c/SF/YP9f4RtNGx/ardLAQO/
+rWm31zLZ9Vdq6YaCPqVmMbMWPcLzJmAy01IesGykNz709a/r4d+ABs8qQedmCeFL
+l+d3vSFtKbZnwy1+7dZ5ZdHPOrbRsV5WYVB6Ws5OUDGAA5hH5+QYfERaxqSzO8bG
+wzrwbMOLyKSRBfP12baqBqG3q+Sx6iEUXIOk/P+2UNOMEiaZdnDpwA+mdPy70Bt4
+znKS4iicvObpCdg604nmvi533wEKb5b25Y08TVJ2Glbhc34XrD2tbKNSEhhw5oBO
+M/J+JjKsBY04pOZ2PJ8QaQ5tndLBeSBrW88zjdGUdjXnXVXHt6woq0bM5zshtQoK
+5EpZ3IE1S0SVEgpnpaH/WwAH0sDM+T/8nzPyAPiMbIedBi3x7+PmBvrFZhNb/FAH
+nnGGstpvdDDPk1Po3CLW3iAfYY2jLqN4MpBs3KwytQXk9TwzDdbgh3cXTJ2w2Amo
+DVf3RIXwyAS+XF1a4xeOVGNpf0l0ZAWMowIDAQABo2MwYTAPBgNVHRMBAf8EBTAD
+AQH/MB8GA1UdIwQYMBaAFLK0ruYt9ybVqnUtdkvAG1Mh0EjvMB0GA1UdDgQWBBSy
+tK7mLfcm1ap1LXZLwBtTIdBI7zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEL
+BQADggIBAImocn+M684uGMQQgC0QDP/7FM0E4BQ8Tpr7nym/Ip5XuYJzEmMmtcyQ
+6dIqKe6cLcwsmb5FJ+Sxce3kOJUxQfJ9emN438o2Fi+CiJ+8EUdPdk3ILY7r3y18
+Tjvarvbj2l0Upq7ohUSdBm6O++96SmotKygY/r+QLHUWnw/qln0F7psTpURs+APQ
+3SPh/QMSEgj0GDSz4DcLdxEBSL9htLX4GdnLTeqjjO/98Aa1bZL0SmFQhO3sSdPk
+vmjmLuMxC1QLGpLWgti2omU8ZgT5Vdps+9u1FGZNlIM7zR6mK7L+d0CGq+ffCsn9
+9t2HVhjYsCxVYJb6CH5SkPVLpi6HfMsg2wY+oF0Dd32iPBMbKaITVaA9FCKvb7jQ
+mhty3QUBjYZgv6Rn7rWlDdF/5horYmbDB7rnoEgcOMPpRfunf/ztAmgayncSd6YA
+VSgU7NbHEqIbZULpkejLPoeJVF3Zr52XnGnnCv8PWniLYypMfUeUP95L6VPQMPHF
+9p5J3zugkaOj/s1YzOrfr28oO6Bpm4/srK4rVJ2bBLFHIK+WEj5jlB0E5y67hscM
+moi/dkfv97ALl2bSRM9gUgfh1SxKOidhd8rXj+eHDjD/DLsE4mHDosiXYY60MGo8
+bcIHX0pzLz/5FooBZu+6kcpSV3uu1OYP3Qt6f4ueJiDPO++BcYNZ
+-----END CERTIFICATE-----
+
+# Issuer: CN=E-Tugra Global Root CA ECC v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center
+# Subject: CN=E-Tugra Global Root CA ECC v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center
+# Label: "E-Tugra Global Root CA ECC v3"
+# Serial: 218504919822255052842371958738296604628416471745
+# MD5 Fingerprint: 46:bc:81:bb:f1:b5:1e:f7:4b:96:bc:14:e2:e7:27:64
+# SHA1 Fingerprint: 8a:2f:af:57:53:b1:b0:e6:a1:04:ec:5b:6a:69:71:6d:f6:1c:e2:84
+# SHA256 Fingerprint: 87:3f:46:85:fa:7f:56:36:25:25:2e:6d:36:bc:d7:f1:6f:c2:49:51:f2:64:e4:7e:1b:95:4f:49:08:cd:ca:13
+-----BEGIN CERTIFICATE-----
+MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMw
+gYAxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVn
+cmEgRUJHIEEuUy4xHTAbBgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYD
+VQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENBIEVDQyB2MzAeFw0yMDAzMTgwOTQ2
+NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEPMA0GA1UEBxMGQW5r
+YXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1Z3Jh
+IFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBF
+Q0MgdjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQ
+KczLWYHMjLiSF4mDKpL2w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YK
+fWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMB
+Af8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQzPUwHQYDVR0OBBYEFP+C
+MXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNp
+ADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/6
+7W4WAie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFx
+vmjkI6TZraE3
+-----END CERTIFICATE-----
+
+# Issuer: CN=Security Communication RootCA3 O=SECOM Trust Systems CO.,LTD.
+# Subject: CN=Security Communication RootCA3 O=SECOM Trust Systems CO.,LTD.
+# Label: "Security Communication RootCA3"
+# Serial: 16247922307909811815
+# MD5 Fingerprint: 1c:9a:16:ff:9e:5c:e0:4d:8a:14:01:f4:35:5d:29:26
+# SHA1 Fingerprint: c3:03:c8:22:74:92:e5:61:a2:9c:5f:79:91:2b:1e:44:13:91:30:3a
+# SHA256 Fingerprint: 24:a5:5c:2a:b0:51:44:2d:06:17:76:65:41:23:9a:4a:d0:32:d7:c5:51:75:aa:34:ff:de:2f:bc:4f:5c:52:94
+-----BEGIN CERTIFICATE-----
+MIIFfzCCA2egAwIBAgIJAOF8N0D9G/5nMA0GCSqGSIb3DQEBDAUAMF0xCzAJBgNV
+BAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScw
+JQYDVQQDEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTMwHhcNMTYwNjE2
+MDYxNzE2WhcNMzgwMTE4MDYxNzE2WjBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
+U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UEAxMeU2VjdXJpdHkg
+Q29tbXVuaWNhdGlvbiBSb290Q0EzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+CgKCAgEA48lySfcw3gl8qUCBWNO0Ot26YQ+TUG5pPDXC7ltzkBtnTCHsXzW7OT4r
+CmDvu20rhvtxosis5FaU+cmvsXLUIKx00rgVrVH+hXShuRD+BYD5UpOzQD11EKzA
+lrenfna84xtSGc4RHwsENPXY9Wk8d/Nk9A2qhd7gCVAEF5aEt8iKvE1y/By7z/MG
+TfmfZPd+pmaGNXHIEYBMwXFAWB6+oHP2/D5Q4eAvJj1+XCO1eXDe+uDRpdYMQXF7
+9+qMHIjH7Iv10S9VlkZ8WjtYO/u62C21Jdp6Ts9EriGmnpjKIG58u4iFW/vAEGK7
+8vknR+/RiTlDxN/e4UG/VHMgly1s2vPUB6PmudhvrvyMGS7TZ2crldtYXLVqAvO4
+g160a75BflcJdURQVc1aEWEhCmHCqYj9E7wtiS/NYeCVvsq1e+F7NGcLH7YMx3we
+GVPKp7FKFSBWFHA9K4IsD50VHUeAR/94mQ4xr28+j+2GaR57GIgUssL8gjMunEst
++3A7caoreyYn8xrC3PsXuKHqy6C0rtOUfnrQq8PsOC0RLoi/1D+tEjtCrI8Cbn3M
+0V9hvqG8OmpI6iZVIhZdXw3/JzOfGAN0iltSIEdrRU0id4xVJ/CvHozJgyJUt5rQ
+T9nO/NkuHJYosQLTA70lUhw0Zk8jq/R3gpYd0VcwCBEF/VfR2ccCAwEAAaNCMEAw
+HQYDVR0OBBYEFGQUfPxYchamCik0FW8qy7z8r6irMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQDcAiMI4u8hOscNtybS
+YpOnpSNyByCCYN8Y11StaSWSntkUz5m5UoHPrmyKO1o5yGwBQ8IibQLwYs1OY0PA
+FNr0Y/Dq9HHuTofjcan0yVflLl8cebsjqodEV+m9NU1Bu0soo5iyG9kLFwfl9+qd
+9XbXv8S2gVj/yP9kaWJ5rW4OH3/uHWnlt3Jxs/6lATWUVCvAUm2PVcTJ0rjLyjQI
+UYWg9by0F1jqClx6vWPGOi//lkkZhOpn2ASxYfQAW0q3nHE3GYV5v4GwxxMOdnE+
+OoAGrgYWp421wsTL/0ClXI2lyTrtcoHKXJg80jQDdwj98ClZXSEIx2C/pHF7uNke
+gr4Jr2VvKKu/S7XuPghHJ6APbw+LP6yVGPO5DtxnVW5inkYO0QR4ynKudtml+LLf
+iAlhi+8kTtFZP1rUPcmTPCtk9YENFpb3ksP+MW/oKjJ0DvRMmEoYDjBU1cXrvMUV
+nuiZIesnKwkK2/HmcBhWuwzkvvnoEKQTkrgc4NtnHVMDpCKn3F2SEDzq//wbEBrD
+2NCcnWXL0CsnMQMeNuE9dnUM/0Umud1RvCPHX9jYhxBAEg09ODfnRDwYwFMJZI//
+1ZqmfHAuc1Uh6N//g7kdPjIe1qZ9LPFm6Vwdp6POXiUyK+OVrCoHzrQoeIY8Laad
+TdJ0MN1kURXbg4NR16/9M51NZg==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD.
+# Subject: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD.
+# Label: "Security Communication ECC RootCA1"
+# Serial: 15446673492073852651
+# MD5 Fingerprint: 7e:43:b0:92:68:ec:05:43:4c:98:ab:5d:35:2e:7e:86
+# SHA1 Fingerprint: b8:0e:26:a9:bf:d2:b2:3b:c0:ef:46:c9:ba:c7:bb:f6:1d:0d:41:41
+# SHA256 Fingerprint: e7:4f:bd:a5:5b:d5:64:c4:73:a3:6b:44:1a:a7:99:c8:a6:8e:07:74:40:e8:28:8b:9f:a1:e5:0e:4b:ba:ca:11
+-----BEGIN CERTIFICATE-----
+MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT
+AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD
+VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx
+NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT
+HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5
+IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
+AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl
+dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK
+ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu
+9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O
+be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k=
+-----END CERTIFICATE-----
diff --git a/src/pip/_vendor/certifi/core.py b/src/pip/_vendor/certifi/core.py
index b8140cf1a..c3e546604 100644
--- a/src/pip/_vendor/certifi/core.py
+++ b/src/pip/_vendor/certifi/core.py
@@ -1,34 +1,20 @@
-# -*- coding: utf-8 -*-
-
"""
certifi.py
~~~~~~~~~~
This module returns the installation location of cacert.pem or its contents.
"""
-import os
-
+import sys
-class _PipPatchedCertificate(Exception):
- pass
+if sys.version_info >= (3, 11):
-try:
- # Return a certificate file on disk for a standalone pip zipapp running in
- # an isolated build environment to use. Passing --cert to the standalone
- # pip does not work since requests calls where() unconditionally on import.
- _PIP_STANDALONE_CERT = os.environ.get("_PIP_STANDALONE_CERT")
- if _PIP_STANDALONE_CERT:
- def where():
- return _PIP_STANDALONE_CERT
- raise _PipPatchedCertificate()
-
- from importlib.resources import path as get_path, read_text
+ from importlib.resources import as_file, files
_CACERT_CTX = None
_CACERT_PATH = None
- def where():
+ def where() -> str:
# This is slightly terrible, but we want to delay extracting the file
# in cases where we're inside of a zipimport situation until someone
# actually calls where(), but we don't want to re-extract the file
@@ -47,30 +33,76 @@ try:
# We also have to hold onto the actual context manager, because
# it will do the cleanup whenever it gets garbage collected, so
# we will also store that at the global level as well.
+ _CACERT_CTX = as_file(files("pip._vendor.certifi").joinpath("cacert.pem"))
+ _CACERT_PATH = str(_CACERT_CTX.__enter__())
+
+ return _CACERT_PATH
+
+ def contents() -> str:
+ return files("pip._vendor.certifi").joinpath("cacert.pem").read_text(encoding="ascii")
+
+elif sys.version_info >= (3, 7):
+
+ from importlib.resources import path as get_path, read_text
+
+ _CACERT_CTX = None
+ _CACERT_PATH = None
+
+ def where() -> str:
+ # This is slightly terrible, but we want to delay extracting the
+ # file in cases where we're inside of a zipimport situation until
+ # someone actually calls where(), but we don't want to re-extract
+ # the file on every call of where(), so we'll do it once then store
+ # it in a global variable.
+ global _CACERT_CTX
+ global _CACERT_PATH
+ if _CACERT_PATH is None:
+ # This is slightly janky, the importlib.resources API wants you
+ # to manage the cleanup of this file, so it doesn't actually
+ # return a path, it returns a context manager that will give
+ # you the path when you enter it and will do any cleanup when
+ # you leave it. In the common case of not needing a temporary
+ # file, it will just return the file system location and the
+ # __exit__() is a no-op.
+ #
+ # We also have to hold onto the actual context manager, because
+ # it will do the cleanup whenever it gets garbage collected, so
+ # we will also store that at the global level as well.
_CACERT_CTX = get_path("pip._vendor.certifi", "cacert.pem")
_CACERT_PATH = str(_CACERT_CTX.__enter__())
return _CACERT_PATH
-except _PipPatchedCertificate:
- pass
+ def contents() -> str:
+ return read_text("pip._vendor.certifi", "cacert.pem", encoding="ascii")
+
+else:
+ import os
+ import types
+ from typing import Union
+
+ Package = Union[types.ModuleType, str]
+ Resource = Union[str, "os.PathLike"]
-except ImportError:
# This fallback will work for Python versions prior to 3.7 that lack the
# importlib.resources module but relies on the existing `where` function
# so won't address issues with environments like PyOxidizer that don't set
# __file__ on modules.
- def read_text(_module, _path, encoding="ascii"):
- with open(where(), "r", encoding=encoding) as data:
+ def read_text(
+ package: Package,
+ resource: Resource,
+ encoding: str = 'utf-8',
+ errors: str = 'strict'
+ ) -> str:
+ with open(where(), encoding=encoding) as data:
return data.read()
# If we don't have importlib.resources, then we will just do the old logic
# of assuming we're on the filesystem and munge the path directly.
- def where():
+ def where() -> str:
f = os.path.dirname(__file__)
return os.path.join(f, "cacert.pem")
-
-def contents():
- return read_text("certifi", "cacert.pem", encoding="ascii")
+ def contents() -> str:
+ return read_text("pip._vendor.certifi", "cacert.pem", encoding="ascii")
diff --git a/src/pip/_vendor/html5lib/filters/__init__.py b/src/pip/_vendor/certifi/py.typed
index e69de29bb..e69de29bb 100644
--- a/src/pip/_vendor/html5lib/filters/__init__.py
+++ b/src/pip/_vendor/certifi/py.typed
diff --git a/src/pip/_vendor/chardet.pyi b/src/pip/_vendor/chardet.pyi
deleted file mode 100644
index 29e87e331..000000000
--- a/src/pip/_vendor/chardet.pyi
+++ /dev/null
@@ -1 +0,0 @@
-from chardet import * \ No newline at end of file
diff --git a/src/pip/_vendor/chardet/LICENSE b/src/pip/_vendor/chardet/LICENSE
index 8add30ad5..4362b4915 100644
--- a/src/pip/_vendor/chardet/LICENSE
+++ b/src/pip/_vendor/chardet/LICENSE
@@ -1,8 +1,8 @@
- GNU LESSER GENERAL PUBLIC LICENSE
- Version 2.1, February 1999
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
- 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -10,7 +10,7 @@
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
- Preamble
+ Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
@@ -112,7 +112,7 @@ modification follow. Pay close attention to the difference between a
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
- GNU LESSER GENERAL PUBLIC LICENSE
+ GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
@@ -146,7 +146,7 @@ such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
-
+
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
@@ -432,7 +432,7 @@ decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
- NO WARRANTY
+ NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
@@ -455,7 +455,7 @@ FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
- END OF TERMS AND CONDITIONS
+ END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
@@ -485,7 +485,7 @@ convey the exclusion of warranty; and each file should have at least the
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
@@ -500,5 +500,3 @@ necessary. Here is a sample; alter the names:
Ty Coon, President of Vice
That's all there is to it!
-
-
diff --git a/src/pip/_vendor/chardet/__init__.py b/src/pip/_vendor/chardet/__init__.py
index 80ad2546d..fe581623d 100644
--- a/src/pip/_vendor/chardet/__init__.py
+++ b/src/pip/_vendor/chardet/__init__.py
@@ -15,69 +15,101 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
+from typing import List, Union
-from .universaldetector import UniversalDetector
+from .charsetgroupprober import CharSetGroupProber
+from .charsetprober import CharSetProber
from .enums import InputState
-from .version import __version__, VERSION
-
+from .resultdict import ResultDict
+from .universaldetector import UniversalDetector
+from .version import VERSION, __version__
-__all__ = ['UniversalDetector', 'detect', 'detect_all', '__version__', 'VERSION']
+__all__ = ["UniversalDetector", "detect", "detect_all", "__version__", "VERSION"]
-def detect(byte_str):
+def detect(
+ byte_str: Union[bytes, bytearray], should_rename_legacy: bool = False
+) -> ResultDict:
"""
Detect the encoding of the given byte string.
:param byte_str: The byte sequence to examine.
:type byte_str: ``bytes`` or ``bytearray``
+ :param should_rename_legacy: Should we rename legacy encodings
+ to their more modern equivalents?
+ :type should_rename_legacy: ``bool``
"""
if not isinstance(byte_str, bytearray):
if not isinstance(byte_str, bytes):
- raise TypeError('Expected object of type bytes or bytearray, got: '
- '{}'.format(type(byte_str)))
- else:
- byte_str = bytearray(byte_str)
- detector = UniversalDetector()
+ raise TypeError(
+ f"Expected object of type bytes or bytearray, got: {type(byte_str)}"
+ )
+ byte_str = bytearray(byte_str)
+ detector = UniversalDetector(should_rename_legacy=should_rename_legacy)
detector.feed(byte_str)
return detector.close()
-def detect_all(byte_str):
+def detect_all(
+ byte_str: Union[bytes, bytearray],
+ ignore_threshold: bool = False,
+ should_rename_legacy: bool = False,
+) -> List[ResultDict]:
"""
Detect all the possible encodings of the given byte string.
- :param byte_str: The byte sequence to examine.
- :type byte_str: ``bytes`` or ``bytearray``
+ :param byte_str: The byte sequence to examine.
+ :type byte_str: ``bytes`` or ``bytearray``
+ :param ignore_threshold: Include encodings that are below
+ ``UniversalDetector.MINIMUM_THRESHOLD``
+ in results.
+ :type ignore_threshold: ``bool``
+ :param should_rename_legacy: Should we rename legacy encodings
+ to their more modern equivalents?
+ :type should_rename_legacy: ``bool``
"""
if not isinstance(byte_str, bytearray):
if not isinstance(byte_str, bytes):
- raise TypeError('Expected object of type bytes or bytearray, got: '
- '{}'.format(type(byte_str)))
- else:
- byte_str = bytearray(byte_str)
+ raise TypeError(
+ f"Expected object of type bytes or bytearray, got: {type(byte_str)}"
+ )
+ byte_str = bytearray(byte_str)
- detector = UniversalDetector()
+ detector = UniversalDetector(should_rename_legacy=should_rename_legacy)
detector.feed(byte_str)
detector.close()
- if detector._input_state == InputState.HIGH_BYTE:
- results = []
- for prober in detector._charset_probers:
- if prober.get_confidence() > detector.MINIMUM_THRESHOLD:
- charset_name = prober.charset_name
- lower_charset_name = prober.charset_name.lower()
+ if detector.input_state == InputState.HIGH_BYTE:
+ results: List[ResultDict] = []
+ probers: List[CharSetProber] = []
+ for prober in detector.charset_probers:
+ if isinstance(prober, CharSetGroupProber):
+ probers.extend(p for p in prober.probers)
+ else:
+ probers.append(prober)
+ for prober in probers:
+ if ignore_threshold or prober.get_confidence() > detector.MINIMUM_THRESHOLD:
+ charset_name = prober.charset_name or ""
+ lower_charset_name = charset_name.lower()
# Use Windows encoding name instead of ISO-8859 if we saw any
# extra Windows-specific bytes
- if lower_charset_name.startswith('iso-8859'):
- if detector._has_win_bytes:
- charset_name = detector.ISO_WIN_MAP.get(lower_charset_name,
- charset_name)
- results.append({
- 'encoding': charset_name,
- 'confidence': prober.get_confidence(),
- 'language': prober.language,
- })
+ if lower_charset_name.startswith("iso-8859") and detector.has_win_bytes:
+ charset_name = detector.ISO_WIN_MAP.get(
+ lower_charset_name, charset_name
+ )
+ # Rename legacy encodings with superset encodings if asked
+ if should_rename_legacy:
+ charset_name = detector.LEGACY_MAP.get(
+ charset_name.lower(), charset_name
+ )
+ results.append(
+ {
+ "encoding": charset_name,
+ "confidence": prober.get_confidence(),
+ "language": prober.language,
+ }
+ )
if len(results) > 0:
- return sorted(results, key=lambda result: -result['confidence'])
+ return sorted(results, key=lambda result: -result["confidence"])
return [detector.result]
diff --git a/src/pip/_vendor/chardet/big5freq.py b/src/pip/_vendor/chardet/big5freq.py
index 38f32517a..87d9f972e 100644
--- a/src/pip/_vendor/chardet/big5freq.py
+++ b/src/pip/_vendor/chardet/big5freq.py
@@ -42,9 +42,9 @@
BIG5_TYPICAL_DISTRIBUTION_RATIO = 0.75
-#Char to FreqOrder table
+# Char to FreqOrder table
BIG5_TABLE_SIZE = 5376
-
+# fmt: off
BIG5_CHAR_TO_FREQ_ORDER = (
1,1801,1506, 255,1431, 198, 9, 82, 6,5008, 177, 202,3681,1256,2821, 110, # 16
3814, 33,3274, 261, 76, 44,2114, 16,2946,2187,1176, 659,3971, 26,3451,2653, # 32
@@ -383,4 +383,4 @@ BIG5_CHAR_TO_FREQ_ORDER = (
890,3669,3943,5791,1878,3798,3439,5792,2186,2358,3440,1652,5793,5794,5795, 941, # 5360
2299, 208,3546,4161,2020, 330,4438,3944,2906,2499,3799,4439,4811,5796,5797,5798, # 5376
)
-
+# fmt: on
diff --git a/src/pip/_vendor/chardet/big5prober.py b/src/pip/_vendor/chardet/big5prober.py
index 98f997012..ef09c60e3 100644
--- a/src/pip/_vendor/chardet/big5prober.py
+++ b/src/pip/_vendor/chardet/big5prober.py
@@ -25,23 +25,23 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
-from .mbcharsetprober import MultiByteCharSetProber
-from .codingstatemachine import CodingStateMachine
from .chardistribution import Big5DistributionAnalysis
+from .codingstatemachine import CodingStateMachine
+from .mbcharsetprober import MultiByteCharSetProber
from .mbcssm import BIG5_SM_MODEL
class Big5Prober(MultiByteCharSetProber):
- def __init__(self):
- super(Big5Prober, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
self.coding_sm = CodingStateMachine(BIG5_SM_MODEL)
self.distribution_analyzer = Big5DistributionAnalysis()
self.reset()
@property
- def charset_name(self):
+ def charset_name(self) -> str:
return "Big5"
@property
- def language(self):
+ def language(self) -> str:
return "Chinese"
diff --git a/src/pip/_vendor/chardet/chardistribution.py b/src/pip/_vendor/chardet/chardistribution.py
index c0395f4a4..176cb9964 100644
--- a/src/pip/_vendor/chardet/chardistribution.py
+++ b/src/pip/_vendor/chardet/chardistribution.py
@@ -25,40 +25,58 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
-from .euctwfreq import (EUCTW_CHAR_TO_FREQ_ORDER, EUCTW_TABLE_SIZE,
- EUCTW_TYPICAL_DISTRIBUTION_RATIO)
-from .euckrfreq import (EUCKR_CHAR_TO_FREQ_ORDER, EUCKR_TABLE_SIZE,
- EUCKR_TYPICAL_DISTRIBUTION_RATIO)
-from .gb2312freq import (GB2312_CHAR_TO_FREQ_ORDER, GB2312_TABLE_SIZE,
- GB2312_TYPICAL_DISTRIBUTION_RATIO)
-from .big5freq import (BIG5_CHAR_TO_FREQ_ORDER, BIG5_TABLE_SIZE,
- BIG5_TYPICAL_DISTRIBUTION_RATIO)
-from .jisfreq import (JIS_CHAR_TO_FREQ_ORDER, JIS_TABLE_SIZE,
- JIS_TYPICAL_DISTRIBUTION_RATIO)
-
-
-class CharDistributionAnalysis(object):
+from typing import Tuple, Union
+
+from .big5freq import (
+ BIG5_CHAR_TO_FREQ_ORDER,
+ BIG5_TABLE_SIZE,
+ BIG5_TYPICAL_DISTRIBUTION_RATIO,
+)
+from .euckrfreq import (
+ EUCKR_CHAR_TO_FREQ_ORDER,
+ EUCKR_TABLE_SIZE,
+ EUCKR_TYPICAL_DISTRIBUTION_RATIO,
+)
+from .euctwfreq import (
+ EUCTW_CHAR_TO_FREQ_ORDER,
+ EUCTW_TABLE_SIZE,
+ EUCTW_TYPICAL_DISTRIBUTION_RATIO,
+)
+from .gb2312freq import (
+ GB2312_CHAR_TO_FREQ_ORDER,
+ GB2312_TABLE_SIZE,
+ GB2312_TYPICAL_DISTRIBUTION_RATIO,
+)
+from .jisfreq import (
+ JIS_CHAR_TO_FREQ_ORDER,
+ JIS_TABLE_SIZE,
+ JIS_TYPICAL_DISTRIBUTION_RATIO,
+)
+from .johabfreq import JOHAB_TO_EUCKR_ORDER_TABLE
+
+
+class CharDistributionAnalysis:
ENOUGH_DATA_THRESHOLD = 1024
SURE_YES = 0.99
SURE_NO = 0.01
MINIMUM_DATA_THRESHOLD = 3
- def __init__(self):
+ def __init__(self) -> None:
# Mapping table to get frequency order from char order (get from
# GetOrder())
- self._char_to_freq_order = None
- self._table_size = None # Size of above table
+ self._char_to_freq_order: Tuple[int, ...] = tuple()
+ self._table_size = 0 # Size of above table
# This is a constant value which varies from language to language,
# used in calculating confidence. See
# http://www.mozilla.org/projects/intl/UniversalCharsetDetection.html
# for further detail.
- self.typical_distribution_ratio = None
- self._done = None
- self._total_chars = None
- self._freq_chars = None
+ self.typical_distribution_ratio = 0.0
+ self._done = False
+ self._total_chars = 0
+ self._freq_chars = 0
self.reset()
- def reset(self):
+ def reset(self) -> None:
"""reset analyser, clear any state"""
# If this flag is set to True, detection is done and conclusion has
# been made
@@ -67,7 +85,7 @@ class CharDistributionAnalysis(object):
# The number of characters whose frequency order is less than 512
self._freq_chars = 0
- def feed(self, char, char_len):
+ def feed(self, char: Union[bytes, bytearray], char_len: int) -> None:
"""feed a character with known length"""
if char_len == 2:
# we only care about 2-bytes character in our distribution analysis
@@ -81,7 +99,7 @@ class CharDistributionAnalysis(object):
if 512 > self._char_to_freq_order[order]:
self._freq_chars += 1
- def get_confidence(self):
+ def get_confidence(self) -> float:
"""return confidence based on existing data"""
# if we didn't receive any character in our consideration range,
# return negative answer
@@ -89,20 +107,21 @@ class CharDistributionAnalysis(object):
return self.SURE_NO
if self._total_chars != self._freq_chars:
- r = (self._freq_chars / ((self._total_chars - self._freq_chars)
- * self.typical_distribution_ratio))
+ r = self._freq_chars / (
+ (self._total_chars - self._freq_chars) * self.typical_distribution_ratio
+ )
if r < self.SURE_YES:
return r
# normalize confidence (we don't want to be 100% sure)
return self.SURE_YES
- def got_enough_data(self):
+ def got_enough_data(self) -> bool:
# It is not necessary to receive all data to draw conclusion.
# For charset detection, certain amount of data is enough
return self._total_chars > self.ENOUGH_DATA_THRESHOLD
- def get_order(self, byte_str):
+ def get_order(self, _: Union[bytes, bytearray]) -> int:
# We do not handle characters based on the original encoding string,
# but convert this encoding string to a number, here called order.
# This allows multiple encodings of a language to share one frequency
@@ -111,13 +130,13 @@ class CharDistributionAnalysis(object):
class EUCTWDistributionAnalysis(CharDistributionAnalysis):
- def __init__(self):
- super(EUCTWDistributionAnalysis, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
self._char_to_freq_order = EUCTW_CHAR_TO_FREQ_ORDER
self._table_size = EUCTW_TABLE_SIZE
self.typical_distribution_ratio = EUCTW_TYPICAL_DISTRIBUTION_RATIO
- def get_order(self, byte_str):
+ def get_order(self, byte_str: Union[bytes, bytearray]) -> int:
# for euc-TW encoding, we are interested
# first byte range: 0xc4 -- 0xfe
# second byte range: 0xa1 -- 0xfe
@@ -125,18 +144,17 @@ class EUCTWDistributionAnalysis(CharDistributionAnalysis):
first_char = byte_str[0]
if first_char >= 0xC4:
return 94 * (first_char - 0xC4) + byte_str[1] - 0xA1
- else:
- return -1
+ return -1
class EUCKRDistributionAnalysis(CharDistributionAnalysis):
- def __init__(self):
- super(EUCKRDistributionAnalysis, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
self._char_to_freq_order = EUCKR_CHAR_TO_FREQ_ORDER
self._table_size = EUCKR_TABLE_SIZE
self.typical_distribution_ratio = EUCKR_TYPICAL_DISTRIBUTION_RATIO
- def get_order(self, byte_str):
+ def get_order(self, byte_str: Union[bytes, bytearray]) -> int:
# for euc-KR encoding, we are interested
# first byte range: 0xb0 -- 0xfe
# second byte range: 0xa1 -- 0xfe
@@ -144,18 +162,32 @@ class EUCKRDistributionAnalysis(CharDistributionAnalysis):
first_char = byte_str[0]
if first_char >= 0xB0:
return 94 * (first_char - 0xB0) + byte_str[1] - 0xA1
- else:
- return -1
+ return -1
+
+
+class JOHABDistributionAnalysis(CharDistributionAnalysis):
+ def __init__(self) -> None:
+ super().__init__()
+ self._char_to_freq_order = EUCKR_CHAR_TO_FREQ_ORDER
+ self._table_size = EUCKR_TABLE_SIZE
+ self.typical_distribution_ratio = EUCKR_TYPICAL_DISTRIBUTION_RATIO
+
+ def get_order(self, byte_str: Union[bytes, bytearray]) -> int:
+ first_char = byte_str[0]
+ if 0x88 <= first_char < 0xD4:
+ code = first_char * 256 + byte_str[1]
+ return JOHAB_TO_EUCKR_ORDER_TABLE.get(code, -1)
+ return -1
class GB2312DistributionAnalysis(CharDistributionAnalysis):
- def __init__(self):
- super(GB2312DistributionAnalysis, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
self._char_to_freq_order = GB2312_CHAR_TO_FREQ_ORDER
self._table_size = GB2312_TABLE_SIZE
self.typical_distribution_ratio = GB2312_TYPICAL_DISTRIBUTION_RATIO
- def get_order(self, byte_str):
+ def get_order(self, byte_str: Union[bytes, bytearray]) -> int:
# for GB2312 encoding, we are interested
# first byte range: 0xb0 -- 0xfe
# second byte range: 0xa1 -- 0xfe
@@ -163,18 +195,17 @@ class GB2312DistributionAnalysis(CharDistributionAnalysis):
first_char, second_char = byte_str[0], byte_str[1]
if (first_char >= 0xB0) and (second_char >= 0xA1):
return 94 * (first_char - 0xB0) + second_char - 0xA1
- else:
- return -1
+ return -1
class Big5DistributionAnalysis(CharDistributionAnalysis):
- def __init__(self):
- super(Big5DistributionAnalysis, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
self._char_to_freq_order = BIG5_CHAR_TO_FREQ_ORDER
self._table_size = BIG5_TABLE_SIZE
self.typical_distribution_ratio = BIG5_TYPICAL_DISTRIBUTION_RATIO
- def get_order(self, byte_str):
+ def get_order(self, byte_str: Union[bytes, bytearray]) -> int:
# for big5 encoding, we are interested
# first byte range: 0xa4 -- 0xfe
# second byte range: 0x40 -- 0x7e , 0xa1 -- 0xfe
@@ -183,28 +214,26 @@ class Big5DistributionAnalysis(CharDistributionAnalysis):
if first_char >= 0xA4:
if second_char >= 0xA1:
return 157 * (first_char - 0xA4) + second_char - 0xA1 + 63
- else:
- return 157 * (first_char - 0xA4) + second_char - 0x40
- else:
- return -1
+ return 157 * (first_char - 0xA4) + second_char - 0x40
+ return -1
class SJISDistributionAnalysis(CharDistributionAnalysis):
- def __init__(self):
- super(SJISDistributionAnalysis, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
self._char_to_freq_order = JIS_CHAR_TO_FREQ_ORDER
self._table_size = JIS_TABLE_SIZE
self.typical_distribution_ratio = JIS_TYPICAL_DISTRIBUTION_RATIO
- def get_order(self, byte_str):
+ def get_order(self, byte_str: Union[bytes, bytearray]) -> int:
# for sjis encoding, we are interested
# first byte range: 0x81 -- 0x9f , 0xe0 -- 0xfe
# second byte range: 0x40 -- 0x7e, 0x81 -- oxfe
# no validation needed here. State machine has done that
first_char, second_char = byte_str[0], byte_str[1]
- if (first_char >= 0x81) and (first_char <= 0x9F):
+ if 0x81 <= first_char <= 0x9F:
order = 188 * (first_char - 0x81)
- elif (first_char >= 0xE0) and (first_char <= 0xEF):
+ elif 0xE0 <= first_char <= 0xEF:
order = 188 * (first_char - 0xE0 + 31)
else:
return -1
@@ -215,19 +244,18 @@ class SJISDistributionAnalysis(CharDistributionAnalysis):
class EUCJPDistributionAnalysis(CharDistributionAnalysis):
- def __init__(self):
- super(EUCJPDistributionAnalysis, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
self._char_to_freq_order = JIS_CHAR_TO_FREQ_ORDER
self._table_size = JIS_TABLE_SIZE
self.typical_distribution_ratio = JIS_TYPICAL_DISTRIBUTION_RATIO
- def get_order(self, byte_str):
+ def get_order(self, byte_str: Union[bytes, bytearray]) -> int:
# for euc-JP encoding, we are interested
# first byte range: 0xa0 -- 0xfe
# second byte range: 0xa1 -- 0xfe
# no validation needed here. State machine has done that
char = byte_str[0]
if char >= 0xA0:
- return 94 * (char - 0xA1) + byte_str[1] - 0xa1
- else:
- return -1
+ return 94 * (char - 0xA1) + byte_str[1] - 0xA1
+ return -1
diff --git a/src/pip/_vendor/chardet/charsetgroupprober.py b/src/pip/_vendor/chardet/charsetgroupprober.py
index 5812cef0b..6def56b4a 100644
--- a/src/pip/_vendor/chardet/charsetgroupprober.py
+++ b/src/pip/_vendor/chardet/charsetgroupprober.py
@@ -25,29 +25,30 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
-from .enums import ProbingState
+from typing import List, Optional, Union
+
from .charsetprober import CharSetProber
+from .enums import LanguageFilter, ProbingState
class CharSetGroupProber(CharSetProber):
- def __init__(self, lang_filter=None):
- super(CharSetGroupProber, self).__init__(lang_filter=lang_filter)
+ def __init__(self, lang_filter: LanguageFilter = LanguageFilter.NONE) -> None:
+ super().__init__(lang_filter=lang_filter)
self._active_num = 0
- self.probers = []
- self._best_guess_prober = None
+ self.probers: List[CharSetProber] = []
+ self._best_guess_prober: Optional[CharSetProber] = None
- def reset(self):
- super(CharSetGroupProber, self).reset()
+ def reset(self) -> None:
+ super().reset()
self._active_num = 0
for prober in self.probers:
- if prober:
- prober.reset()
- prober.active = True
- self._active_num += 1
+ prober.reset()
+ prober.active = True
+ self._active_num += 1
self._best_guess_prober = None
@property
- def charset_name(self):
+ def charset_name(self) -> Optional[str]:
if not self._best_guess_prober:
self.get_confidence()
if not self._best_guess_prober:
@@ -55,17 +56,15 @@ class CharSetGroupProber(CharSetProber):
return self._best_guess_prober.charset_name
@property
- def language(self):
+ def language(self) -> Optional[str]:
if not self._best_guess_prober:
self.get_confidence()
if not self._best_guess_prober:
return None
return self._best_guess_prober.language
- def feed(self, byte_str):
+ def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState:
for prober in self.probers:
- if not prober:
- continue
if not prober.active:
continue
state = prober.feed(byte_str)
@@ -75,7 +74,7 @@ class CharSetGroupProber(CharSetProber):
self._best_guess_prober = prober
self._state = ProbingState.FOUND_IT
return self.state
- elif state == ProbingState.NOT_ME:
+ if state == ProbingState.NOT_ME:
prober.active = False
self._active_num -= 1
if self._active_num <= 0:
@@ -83,22 +82,22 @@ class CharSetGroupProber(CharSetProber):
return self.state
return self.state
- def get_confidence(self):
+ def get_confidence(self) -> float:
state = self.state
if state == ProbingState.FOUND_IT:
return 0.99
- elif state == ProbingState.NOT_ME:
+ if state == ProbingState.NOT_ME:
return 0.01
best_conf = 0.0
self._best_guess_prober = None
for prober in self.probers:
- if not prober:
- continue
if not prober.active:
- self.logger.debug('%s not active', prober.charset_name)
+ self.logger.debug("%s not active", prober.charset_name)
continue
conf = prober.get_confidence()
- self.logger.debug('%s %s confidence = %s', prober.charset_name, prober.language, conf)
+ self.logger.debug(
+ "%s %s confidence = %s", prober.charset_name, prober.language, conf
+ )
if best_conf < conf:
best_conf = conf
self._best_guess_prober = prober
diff --git a/src/pip/_vendor/chardet/charsetprober.py b/src/pip/_vendor/chardet/charsetprober.py
index eac4e5986..a103ca113 100644
--- a/src/pip/_vendor/chardet/charsetprober.py
+++ b/src/pip/_vendor/chardet/charsetprober.py
@@ -28,54 +28,62 @@
import logging
import re
+from typing import Optional, Union
-from .enums import ProbingState
+from .enums import LanguageFilter, ProbingState
+INTERNATIONAL_WORDS_PATTERN = re.compile(
+ b"[a-zA-Z]*[\x80-\xFF]+[a-zA-Z]*[^a-zA-Z\x80-\xFF]?"
+)
-class CharSetProber(object):
+
+class CharSetProber:
SHORTCUT_THRESHOLD = 0.95
- def __init__(self, lang_filter=None):
- self._state = None
+ def __init__(self, lang_filter: LanguageFilter = LanguageFilter.NONE) -> None:
+ self._state = ProbingState.DETECTING
+ self.active = True
self.lang_filter = lang_filter
self.logger = logging.getLogger(__name__)
- def reset(self):
+ def reset(self) -> None:
self._state = ProbingState.DETECTING
@property
- def charset_name(self):
+ def charset_name(self) -> Optional[str]:
return None
- def feed(self, buf):
- pass
+ @property
+ def language(self) -> Optional[str]:
+ raise NotImplementedError
+
+ def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState:
+ raise NotImplementedError
@property
- def state(self):
+ def state(self) -> ProbingState:
return self._state
- def get_confidence(self):
+ def get_confidence(self) -> float:
return 0.0
@staticmethod
- def filter_high_byte_only(buf):
- buf = re.sub(b'([\x00-\x7F])+', b' ', buf)
+ def filter_high_byte_only(buf: Union[bytes, bytearray]) -> bytes:
+ buf = re.sub(b"([\x00-\x7F])+", b" ", buf)
return buf
@staticmethod
- def filter_international_words(buf):
+ def filter_international_words(buf: Union[bytes, bytearray]) -> bytearray:
"""
We define three types of bytes:
alphabet: english alphabets [a-zA-Z]
international: international characters [\x80-\xFF]
marker: everything else [^a-zA-Z\x80-\xFF]
-
The input buffer can be thought to contain a series of words delimited
by markers. This function works to filter all words that contain at
least one international character. All contiguous sequences of markers
are replaced by a single space ascii character.
-
This filter applies to all scripts which do not use English characters.
"""
filtered = bytearray()
@@ -83,8 +91,7 @@ class CharSetProber(object):
# This regex expression filters out only words that have at-least one
# international character. The word may include one marker character at
# the end.
- words = re.findall(b'[a-zA-Z]*[\x80-\xFF]+[a-zA-Z]*[^a-zA-Z\x80-\xFF]?',
- buf)
+ words = INTERNATIONAL_WORDS_PATTERN.findall(buf)
for word in words:
filtered.extend(word[:-1])
@@ -94,20 +101,17 @@ class CharSetProber(object):
# similarly across all languages and may thus have similar
# frequencies).
last_char = word[-1:]
- if not last_char.isalpha() and last_char < b'\x80':
- last_char = b' '
+ if not last_char.isalpha() and last_char < b"\x80":
+ last_char = b" "
filtered.extend(last_char)
return filtered
@staticmethod
- def filter_with_english_letters(buf):
+ def remove_xml_tags(buf: Union[bytes, bytearray]) -> bytes:
"""
Returns a copy of ``buf`` that retains only the sequences of English
alphabet and high byte characters that are not between <> characters.
- Also retains English alphabet and high byte characters immediately
- before occurrences of >.
-
This filter can be applied to all scripts which contain both English
characters and extended ASCII characters, but is currently only used by
``Latin1Prober``.
@@ -115,26 +119,24 @@ class CharSetProber(object):
filtered = bytearray()
in_tag = False
prev = 0
+ buf = memoryview(buf).cast("c")
- for curr in range(len(buf)):
- # Slice here to get bytes instead of an int with Python 3
- buf_char = buf[curr:curr + 1]
- # Check if we're coming out of or entering an HTML tag
- if buf_char == b'>':
- in_tag = False
- elif buf_char == b'<':
- in_tag = True
+ for curr, buf_char in enumerate(buf):
+ # Check if we're coming out of or entering an XML tag
- # If current character is not extended-ASCII and not alphabetic...
- if buf_char < b'\x80' and not buf_char.isalpha():
- # ...and we're not in a tag
+ # https://github.com/python/typeshed/issues/8182
+ if buf_char == b">": # type: ignore[comparison-overlap]
+ prev = curr + 1
+ in_tag = False
+ # https://github.com/python/typeshed/issues/8182
+ elif buf_char == b"<": # type: ignore[comparison-overlap]
if curr > prev and not in_tag:
# Keep everything after last non-extended-ASCII,
# non-alphabetic character
filtered.extend(buf[prev:curr])
# Output a space to delimit stretch we kept
- filtered.extend(b' ')
- prev = curr + 1
+ filtered.extend(b" ")
+ in_tag = True
# If we're not in a tag...
if not in_tag:
diff --git a/src/pip/_vendor/chardet/cli/__init__.py b/src/pip/_vendor/chardet/cli/__init__.py
index 8b1378917..e69de29bb 100644
--- a/src/pip/_vendor/chardet/cli/__init__.py
+++ b/src/pip/_vendor/chardet/cli/__init__.py
@@ -1 +0,0 @@
-
diff --git a/src/pip/_vendor/chardet/cli/chardetect.py b/src/pip/_vendor/chardet/cli/chardetect.py
index 6d6f93aab..43f6e144f 100644
--- a/src/pip/_vendor/chardet/cli/chardetect.py
+++ b/src/pip/_vendor/chardet/cli/chardetect.py
@@ -12,17 +12,21 @@ If no paths are provided, it takes its input from stdin.
"""
-from __future__ import absolute_import, print_function, unicode_literals
import argparse
import sys
+from typing import Iterable, List, Optional
-from pip._vendor.chardet import __version__
-from pip._vendor.chardet.compat import PY2
-from pip._vendor.chardet.universaldetector import UniversalDetector
+from .. import __version__
+from ..universaldetector import UniversalDetector
-def description_of(lines, name='stdin'):
+def description_of(
+ lines: Iterable[bytes],
+ name: str = "stdin",
+ minimal: bool = False,
+ should_rename_legacy: bool = False,
+) -> Optional[str]:
"""
Return a string describing the probable encoding of a file or
list of strings.
@@ -31,8 +35,11 @@ def description_of(lines, name='stdin'):
:type lines: Iterable of bytes
:param name: Name of file or collection of lines
:type name: str
+ :param should_rename_legacy: Should we rename legacy encodings to
+ their more modern equivalents?
+ :type should_rename_legacy: ``bool``
"""
- u = UniversalDetector()
+ u = UniversalDetector(should_rename_legacy=should_rename_legacy)
for line in lines:
line = bytearray(line)
u.feed(line)
@@ -41,16 +48,14 @@ def description_of(lines, name='stdin'):
break
u.close()
result = u.result
- if PY2:
- name = name.decode(sys.getfilesystemencoding(), 'ignore')
- if result['encoding']:
- return '{}: {} with confidence {}'.format(name, result['encoding'],
- result['confidence'])
- else:
- return '{}: no result'.format(name)
+ if minimal:
+ return result["encoding"]
+ if result["encoding"]:
+ return f'{name}: {result["encoding"]} with confidence {result["confidence"]}'
+ return f"{name}: no result"
-def main(argv=None):
+def main(argv: Optional[List[str]] = None) -> None:
"""
Handles command line arguments and gets things started.
@@ -60,25 +65,48 @@ def main(argv=None):
"""
# Get command line arguments
parser = argparse.ArgumentParser(
- description="Takes one or more file paths and reports their detected \
- encodings")
- parser.add_argument('input',
- help='File whose encoding we would like to determine. \
- (default: stdin)',
- type=argparse.FileType('rb'), nargs='*',
- default=[sys.stdin if PY2 else sys.stdin.buffer])
- parser.add_argument('--version', action='version',
- version='%(prog)s {}'.format(__version__))
+ description=(
+ "Takes one or more file paths and reports their detected encodings"
+ )
+ )
+ parser.add_argument(
+ "input",
+ help="File whose encoding we would like to determine. (default: stdin)",
+ type=argparse.FileType("rb"),
+ nargs="*",
+ default=[sys.stdin.buffer],
+ )
+ parser.add_argument(
+ "--minimal",
+ help="Print only the encoding to standard output",
+ action="store_true",
+ )
+ parser.add_argument(
+ "-l",
+ "--legacy",
+ help="Rename legacy encodings to more modern ones.",
+ action="store_true",
+ )
+ parser.add_argument(
+ "--version", action="version", version=f"%(prog)s {__version__}"
+ )
args = parser.parse_args(argv)
for f in args.input:
if f.isatty():
- print("You are running chardetect interactively. Press " +
- "CTRL-D twice at the start of a blank line to signal the " +
- "end of your input. If you want help, run chardetect " +
- "--help\n", file=sys.stderr)
- print(description_of(f, f.name))
-
-
-if __name__ == '__main__':
+ print(
+ "You are running chardetect interactively. Press "
+ "CTRL-D twice at the start of a blank line to signal the "
+ "end of your input. If you want help, run chardetect "
+ "--help\n",
+ file=sys.stderr,
+ )
+ print(
+ description_of(
+ f, f.name, minimal=args.minimal, should_rename_legacy=args.legacy
+ )
+ )
+
+
+if __name__ == "__main__":
main()
diff --git a/src/pip/_vendor/chardet/codingstatemachine.py b/src/pip/_vendor/chardet/codingstatemachine.py
index 68fba44f1..8ed4a8773 100644
--- a/src/pip/_vendor/chardet/codingstatemachine.py
+++ b/src/pip/_vendor/chardet/codingstatemachine.py
@@ -27,10 +27,11 @@
import logging
+from .codingstatemachinedict import CodingStateMachineDict
from .enums import MachineState
-class CodingStateMachine(object):
+class CodingStateMachine:
"""
A state machine to verify a byte sequence for a particular encoding. For
each byte the detector receives, it will feed that byte to every active
@@ -52,37 +53,38 @@ class CodingStateMachine(object):
negative answer for this encoding. Detector will exclude this
encoding from consideration from here on.
"""
- def __init__(self, sm):
+
+ def __init__(self, sm: CodingStateMachineDict) -> None:
self._model = sm
self._curr_byte_pos = 0
self._curr_char_len = 0
- self._curr_state = None
+ self._curr_state = MachineState.START
+ self.active = True
self.logger = logging.getLogger(__name__)
self.reset()
- def reset(self):
+ def reset(self) -> None:
self._curr_state = MachineState.START
- def next_state(self, c):
+ def next_state(self, c: int) -> int:
# for each byte we get its class
# if it is first byte, we also get byte length
- byte_class = self._model['class_table'][c]
+ byte_class = self._model["class_table"][c]
if self._curr_state == MachineState.START:
self._curr_byte_pos = 0
- self._curr_char_len = self._model['char_len_table'][byte_class]
+ self._curr_char_len = self._model["char_len_table"][byte_class]
# from byte's class and state_table, we get its next state
- curr_state = (self._curr_state * self._model['class_factor']
- + byte_class)
- self._curr_state = self._model['state_table'][curr_state]
+ curr_state = self._curr_state * self._model["class_factor"] + byte_class
+ self._curr_state = self._model["state_table"][curr_state]
self._curr_byte_pos += 1
return self._curr_state
- def get_current_charlen(self):
+ def get_current_charlen(self) -> int:
return self._curr_char_len
- def get_coding_state_machine(self):
- return self._model['name']
+ def get_coding_state_machine(self) -> str:
+ return self._model["name"]
@property
- def language(self):
- return self._model['language']
+ def language(self) -> str:
+ return self._model["language"]
diff --git a/src/pip/_vendor/chardet/codingstatemachinedict.py b/src/pip/_vendor/chardet/codingstatemachinedict.py
new file mode 100644
index 000000000..7a3c4c7e3
--- /dev/null
+++ b/src/pip/_vendor/chardet/codingstatemachinedict.py
@@ -0,0 +1,19 @@
+from typing import TYPE_CHECKING, Tuple
+
+if TYPE_CHECKING:
+ # TypedDict was introduced in Python 3.8.
+ #
+ # TODO: Remove the else block and TYPE_CHECKING check when dropping support
+ # for Python 3.7.
+ from typing import TypedDict
+
+ class CodingStateMachineDict(TypedDict, total=False):
+ class_table: Tuple[int, ...]
+ class_factor: int
+ state_table: Tuple[int, ...]
+ char_len_table: Tuple[int, ...]
+ name: str
+ language: str # Optional key
+
+else:
+ CodingStateMachineDict = dict
diff --git a/src/pip/_vendor/chardet/cp949prober.py b/src/pip/_vendor/chardet/cp949prober.py
index efd793abc..fa7307ed8 100644
--- a/src/pip/_vendor/chardet/cp949prober.py
+++ b/src/pip/_vendor/chardet/cp949prober.py
@@ -32,8 +32,8 @@ from .mbcssm import CP949_SM_MODEL
class CP949Prober(MultiByteCharSetProber):
- def __init__(self):
- super(CP949Prober, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
self.coding_sm = CodingStateMachine(CP949_SM_MODEL)
# NOTE: CP949 is a superset of EUC-KR, so the distribution should be
# not different.
@@ -41,9 +41,9 @@ class CP949Prober(MultiByteCharSetProber):
self.reset()
@property
- def charset_name(self):
+ def charset_name(self) -> str:
return "CP949"
@property
- def language(self):
+ def language(self) -> str:
return "Korean"
diff --git a/src/pip/_vendor/chardet/enums.py b/src/pip/_vendor/chardet/enums.py
index 045120722..5e3e19823 100644
--- a/src/pip/_vendor/chardet/enums.py
+++ b/src/pip/_vendor/chardet/enums.py
@@ -4,21 +4,26 @@ All of the Enums that are used throughout the chardet package.
:author: Dan Blanchard (dan.blanchard@gmail.com)
"""
+from enum import Enum, Flag
-class InputState(object):
+
+class InputState:
"""
This enum represents the different states a universal detector can be in.
"""
+
PURE_ASCII = 0
ESC_ASCII = 1
HIGH_BYTE = 2
-class LanguageFilter(object):
+class LanguageFilter(Flag):
"""
This enum represents the different language filters we can apply to a
``UniversalDetector``.
"""
+
+ NONE = 0x00
CHINESE_SIMPLIFIED = 0x01
CHINESE_TRADITIONAL = 0x02
JAPANESE = 0x04
@@ -29,46 +34,50 @@ class LanguageFilter(object):
CJK = CHINESE | JAPANESE | KOREAN
-class ProbingState(object):
+class ProbingState(Enum):
"""
This enum represents the different states a prober can be in.
"""
+
DETECTING = 0
FOUND_IT = 1
NOT_ME = 2
-class MachineState(object):
+class MachineState:
"""
This enum represents the different states a state machine can be in.
"""
+
START = 0
ERROR = 1
ITS_ME = 2
-class SequenceLikelihood(object):
+class SequenceLikelihood:
"""
This enum represents the likelihood of a character following the previous one.
"""
+
NEGATIVE = 0
UNLIKELY = 1
LIKELY = 2
POSITIVE = 3
@classmethod
- def get_num_categories(cls):
+ def get_num_categories(cls) -> int:
""":returns: The number of likelihood categories in the enum."""
return 4
-class CharacterCategory(object):
+class CharacterCategory:
"""
This enum represents the different categories language models for
``SingleByteCharsetProber`` put characters into.
Anything less than CONTROL is considered a letter.
"""
+
UNDEFINED = 255
LINE_BREAK = 254
SYMBOL = 253
diff --git a/src/pip/_vendor/chardet/escprober.py b/src/pip/_vendor/chardet/escprober.py
index c70493f2b..fd713830d 100644
--- a/src/pip/_vendor/chardet/escprober.py
+++ b/src/pip/_vendor/chardet/escprober.py
@@ -25,11 +25,17 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
+from typing import Optional, Union
+
from .charsetprober import CharSetProber
from .codingstatemachine import CodingStateMachine
-from .enums import LanguageFilter, ProbingState, MachineState
-from .escsm import (HZ_SM_MODEL, ISO2022CN_SM_MODEL, ISO2022JP_SM_MODEL,
- ISO2022KR_SM_MODEL)
+from .enums import LanguageFilter, MachineState, ProbingState
+from .escsm import (
+ HZ_SM_MODEL,
+ ISO2022CN_SM_MODEL,
+ ISO2022JP_SM_MODEL,
+ ISO2022KR_SM_MODEL,
+)
class EscCharSetProber(CharSetProber):
@@ -39,8 +45,8 @@ class EscCharSetProber(CharSetProber):
identify these encodings.
"""
- def __init__(self, lang_filter=None):
- super(EscCharSetProber, self).__init__(lang_filter=lang_filter)
+ def __init__(self, lang_filter: LanguageFilter = LanguageFilter.NONE) -> None:
+ super().__init__(lang_filter=lang_filter)
self.coding_sm = []
if self.lang_filter & LanguageFilter.CHINESE_SIMPLIFIED:
self.coding_sm.append(CodingStateMachine(HZ_SM_MODEL))
@@ -49,17 +55,15 @@ class EscCharSetProber(CharSetProber):
self.coding_sm.append(CodingStateMachine(ISO2022JP_SM_MODEL))
if self.lang_filter & LanguageFilter.KOREAN:
self.coding_sm.append(CodingStateMachine(ISO2022KR_SM_MODEL))
- self.active_sm_count = None
- self._detected_charset = None
- self._detected_language = None
- self._state = None
+ self.active_sm_count = 0
+ self._detected_charset: Optional[str] = None
+ self._detected_language: Optional[str] = None
+ self._state = ProbingState.DETECTING
self.reset()
- def reset(self):
- super(EscCharSetProber, self).reset()
+ def reset(self) -> None:
+ super().reset()
for coding_sm in self.coding_sm:
- if not coding_sm:
- continue
coding_sm.active = True
coding_sm.reset()
self.active_sm_count = len(self.coding_sm)
@@ -67,23 +71,20 @@ class EscCharSetProber(CharSetProber):
self._detected_language = None
@property
- def charset_name(self):
+ def charset_name(self) -> Optional[str]:
return self._detected_charset
@property
- def language(self):
+ def language(self) -> Optional[str]:
return self._detected_language
- def get_confidence(self):
- if self._detected_charset:
- return 0.99
- else:
- return 0.00
+ def get_confidence(self) -> float:
+ return 0.99 if self._detected_charset else 0.00
- def feed(self, byte_str):
+ def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState:
for c in byte_str:
for coding_sm in self.coding_sm:
- if not coding_sm or not coding_sm.active:
+ if not coding_sm.active:
continue
coding_state = coding_sm.next_state(c)
if coding_state == MachineState.ERROR:
diff --git a/src/pip/_vendor/chardet/escsm.py b/src/pip/_vendor/chardet/escsm.py
index 0069523a0..11d4adf77 100644
--- a/src/pip/_vendor/chardet/escsm.py
+++ b/src/pip/_vendor/chardet/escsm.py
@@ -12,7 +12,7 @@
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
+# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -20,227 +20,242 @@
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
+from .codingstatemachinedict import CodingStateMachineDict
from .enums import MachineState
+# fmt: off
HZ_CLS = (
-1,0,0,0,0,0,0,0, # 00 - 07
-0,0,0,0,0,0,0,0, # 08 - 0f
-0,0,0,0,0,0,0,0, # 10 - 17
-0,0,0,1,0,0,0,0, # 18 - 1f
-0,0,0,0,0,0,0,0, # 20 - 27
-0,0,0,0,0,0,0,0, # 28 - 2f
-0,0,0,0,0,0,0,0, # 30 - 37
-0,0,0,0,0,0,0,0, # 38 - 3f
-0,0,0,0,0,0,0,0, # 40 - 47
-0,0,0,0,0,0,0,0, # 48 - 4f
-0,0,0,0,0,0,0,0, # 50 - 57
-0,0,0,0,0,0,0,0, # 58 - 5f
-0,0,0,0,0,0,0,0, # 60 - 67
-0,0,0,0,0,0,0,0, # 68 - 6f
-0,0,0,0,0,0,0,0, # 70 - 77
-0,0,0,4,0,5,2,0, # 78 - 7f
-1,1,1,1,1,1,1,1, # 80 - 87
-1,1,1,1,1,1,1,1, # 88 - 8f
-1,1,1,1,1,1,1,1, # 90 - 97
-1,1,1,1,1,1,1,1, # 98 - 9f
-1,1,1,1,1,1,1,1, # a0 - a7
-1,1,1,1,1,1,1,1, # a8 - af
-1,1,1,1,1,1,1,1, # b0 - b7
-1,1,1,1,1,1,1,1, # b8 - bf
-1,1,1,1,1,1,1,1, # c0 - c7
-1,1,1,1,1,1,1,1, # c8 - cf
-1,1,1,1,1,1,1,1, # d0 - d7
-1,1,1,1,1,1,1,1, # d8 - df
-1,1,1,1,1,1,1,1, # e0 - e7
-1,1,1,1,1,1,1,1, # e8 - ef
-1,1,1,1,1,1,1,1, # f0 - f7
-1,1,1,1,1,1,1,1, # f8 - ff
+ 1, 0, 0, 0, 0, 0, 0, 0, # 00 - 07
+ 0, 0, 0, 0, 0, 0, 0, 0, # 08 - 0f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 10 - 17
+ 0, 0, 0, 1, 0, 0, 0, 0, # 18 - 1f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 20 - 27
+ 0, 0, 0, 0, 0, 0, 0, 0, # 28 - 2f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 30 - 37
+ 0, 0, 0, 0, 0, 0, 0, 0, # 38 - 3f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 40 - 47
+ 0, 0, 0, 0, 0, 0, 0, 0, # 48 - 4f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 50 - 57
+ 0, 0, 0, 0, 0, 0, 0, 0, # 58 - 5f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 60 - 67
+ 0, 0, 0, 0, 0, 0, 0, 0, # 68 - 6f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 70 - 77
+ 0, 0, 0, 4, 0, 5, 2, 0, # 78 - 7f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 80 - 87
+ 1, 1, 1, 1, 1, 1, 1, 1, # 88 - 8f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 90 - 97
+ 1, 1, 1, 1, 1, 1, 1, 1, # 98 - 9f
+ 1, 1, 1, 1, 1, 1, 1, 1, # a0 - a7
+ 1, 1, 1, 1, 1, 1, 1, 1, # a8 - af
+ 1, 1, 1, 1, 1, 1, 1, 1, # b0 - b7
+ 1, 1, 1, 1, 1, 1, 1, 1, # b8 - bf
+ 1, 1, 1, 1, 1, 1, 1, 1, # c0 - c7
+ 1, 1, 1, 1, 1, 1, 1, 1, # c8 - cf
+ 1, 1, 1, 1, 1, 1, 1, 1, # d0 - d7
+ 1, 1, 1, 1, 1, 1, 1, 1, # d8 - df
+ 1, 1, 1, 1, 1, 1, 1, 1, # e0 - e7
+ 1, 1, 1, 1, 1, 1, 1, 1, # e8 - ef
+ 1, 1, 1, 1, 1, 1, 1, 1, # f0 - f7
+ 1, 1, 1, 1, 1, 1, 1, 1, # f8 - ff
)
HZ_ST = (
-MachineState.START,MachineState.ERROR, 3,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,# 00-07
-MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 08-0f
-MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START, 4,MachineState.ERROR,# 10-17
- 5,MachineState.ERROR, 6,MachineState.ERROR, 5, 5, 4,MachineState.ERROR,# 18-1f
- 4,MachineState.ERROR, 4, 4, 4,MachineState.ERROR, 4,MachineState.ERROR,# 20-27
- 4,MachineState.ITS_ME,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 28-2f
+MachineState.START, MachineState.ERROR, 3, MachineState.START, MachineState.START, MachineState.START, MachineState.ERROR, MachineState.ERROR, # 00-07
+MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, # 08-0f
+MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ERROR, MachineState.ERROR, MachineState.START, MachineState.START, 4, MachineState.ERROR, # 10-17
+ 5, MachineState.ERROR, 6, MachineState.ERROR, 5, 5, 4, MachineState.ERROR, # 18-1f
+ 4, MachineState.ERROR, 4, 4, 4, MachineState.ERROR, 4, MachineState.ERROR, # 20-27
+ 4, MachineState.ITS_ME, MachineState.START, MachineState.START, MachineState.START, MachineState.START, MachineState.START, MachineState.START, # 28-2f
)
+# fmt: on
HZ_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0)
-HZ_SM_MODEL = {'class_table': HZ_CLS,
- 'class_factor': 6,
- 'state_table': HZ_ST,
- 'char_len_table': HZ_CHAR_LEN_TABLE,
- 'name': "HZ-GB-2312",
- 'language': 'Chinese'}
+HZ_SM_MODEL: CodingStateMachineDict = {
+ "class_table": HZ_CLS,
+ "class_factor": 6,
+ "state_table": HZ_ST,
+ "char_len_table": HZ_CHAR_LEN_TABLE,
+ "name": "HZ-GB-2312",
+ "language": "Chinese",
+}
+# fmt: off
ISO2022CN_CLS = (
-2,0,0,0,0,0,0,0, # 00 - 07
-0,0,0,0,0,0,0,0, # 08 - 0f
-0,0,0,0,0,0,0,0, # 10 - 17
-0,0,0,1,0,0,0,0, # 18 - 1f
-0,0,0,0,0,0,0,0, # 20 - 27
-0,3,0,0,0,0,0,0, # 28 - 2f
-0,0,0,0,0,0,0,0, # 30 - 37
-0,0,0,0,0,0,0,0, # 38 - 3f
-0,0,0,4,0,0,0,0, # 40 - 47
-0,0,0,0,0,0,0,0, # 48 - 4f
-0,0,0,0,0,0,0,0, # 50 - 57
-0,0,0,0,0,0,0,0, # 58 - 5f
-0,0,0,0,0,0,0,0, # 60 - 67
-0,0,0,0,0,0,0,0, # 68 - 6f
-0,0,0,0,0,0,0,0, # 70 - 77
-0,0,0,0,0,0,0,0, # 78 - 7f
-2,2,2,2,2,2,2,2, # 80 - 87
-2,2,2,2,2,2,2,2, # 88 - 8f
-2,2,2,2,2,2,2,2, # 90 - 97
-2,2,2,2,2,2,2,2, # 98 - 9f
-2,2,2,2,2,2,2,2, # a0 - a7
-2,2,2,2,2,2,2,2, # a8 - af
-2,2,2,2,2,2,2,2, # b0 - b7
-2,2,2,2,2,2,2,2, # b8 - bf
-2,2,2,2,2,2,2,2, # c0 - c7
-2,2,2,2,2,2,2,2, # c8 - cf
-2,2,2,2,2,2,2,2, # d0 - d7
-2,2,2,2,2,2,2,2, # d8 - df
-2,2,2,2,2,2,2,2, # e0 - e7
-2,2,2,2,2,2,2,2, # e8 - ef
-2,2,2,2,2,2,2,2, # f0 - f7
-2,2,2,2,2,2,2,2, # f8 - ff
+ 2, 0, 0, 0, 0, 0, 0, 0, # 00 - 07
+ 0, 0, 0, 0, 0, 0, 0, 0, # 08 - 0f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 10 - 17
+ 0, 0, 0, 1, 0, 0, 0, 0, # 18 - 1f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 20 - 27
+ 0, 3, 0, 0, 0, 0, 0, 0, # 28 - 2f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 30 - 37
+ 0, 0, 0, 0, 0, 0, 0, 0, # 38 - 3f
+ 0, 0, 0, 4, 0, 0, 0, 0, # 40 - 47
+ 0, 0, 0, 0, 0, 0, 0, 0, # 48 - 4f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 50 - 57
+ 0, 0, 0, 0, 0, 0, 0, 0, # 58 - 5f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 60 - 67
+ 0, 0, 0, 0, 0, 0, 0, 0, # 68 - 6f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 70 - 77
+ 0, 0, 0, 0, 0, 0, 0, 0, # 78 - 7f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 80 - 87
+ 2, 2, 2, 2, 2, 2, 2, 2, # 88 - 8f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 90 - 97
+ 2, 2, 2, 2, 2, 2, 2, 2, # 98 - 9f
+ 2, 2, 2, 2, 2, 2, 2, 2, # a0 - a7
+ 2, 2, 2, 2, 2, 2, 2, 2, # a8 - af
+ 2, 2, 2, 2, 2, 2, 2, 2, # b0 - b7
+ 2, 2, 2, 2, 2, 2, 2, 2, # b8 - bf
+ 2, 2, 2, 2, 2, 2, 2, 2, # c0 - c7
+ 2, 2, 2, 2, 2, 2, 2, 2, # c8 - cf
+ 2, 2, 2, 2, 2, 2, 2, 2, # d0 - d7
+ 2, 2, 2, 2, 2, 2, 2, 2, # d8 - df
+ 2, 2, 2, 2, 2, 2, 2, 2, # e0 - e7
+ 2, 2, 2, 2, 2, 2, 2, 2, # e8 - ef
+ 2, 2, 2, 2, 2, 2, 2, 2, # f0 - f7
+ 2, 2, 2, 2, 2, 2, 2, 2, # f8 - ff
)
ISO2022CN_ST = (
-MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 00-07
-MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 08-0f
-MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 10-17
-MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 4,MachineState.ERROR,# 18-1f
-MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 20-27
- 5, 6,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 28-2f
-MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 30-37
-MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,# 38-3f
+ MachineState.START, 3, MachineState.ERROR, MachineState.START, MachineState.START, MachineState.START, MachineState.START, MachineState.START, # 00-07
+ MachineState.START, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, # 08-0f
+ MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, # 10-17
+ MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, 4, MachineState.ERROR, # 18-1f
+ MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, # 20-27
+ 5, 6, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, # 28-2f
+ MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, # 30-37
+ MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ERROR, MachineState.START, # 38-3f
)
+# fmt: on
ISO2022CN_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0, 0, 0, 0)
-ISO2022CN_SM_MODEL = {'class_table': ISO2022CN_CLS,
- 'class_factor': 9,
- 'state_table': ISO2022CN_ST,
- 'char_len_table': ISO2022CN_CHAR_LEN_TABLE,
- 'name': "ISO-2022-CN",
- 'language': 'Chinese'}
+ISO2022CN_SM_MODEL: CodingStateMachineDict = {
+ "class_table": ISO2022CN_CLS,
+ "class_factor": 9,
+ "state_table": ISO2022CN_ST,
+ "char_len_table": ISO2022CN_CHAR_LEN_TABLE,
+ "name": "ISO-2022-CN",
+ "language": "Chinese",
+}
+# fmt: off
ISO2022JP_CLS = (
-2,0,0,0,0,0,0,0, # 00 - 07
-0,0,0,0,0,0,2,2, # 08 - 0f
-0,0,0,0,0,0,0,0, # 10 - 17
-0,0,0,1,0,0,0,0, # 18 - 1f
-0,0,0,0,7,0,0,0, # 20 - 27
-3,0,0,0,0,0,0,0, # 28 - 2f
-0,0,0,0,0,0,0,0, # 30 - 37
-0,0,0,0,0,0,0,0, # 38 - 3f
-6,0,4,0,8,0,0,0, # 40 - 47
-0,9,5,0,0,0,0,0, # 48 - 4f
-0,0,0,0,0,0,0,0, # 50 - 57
-0,0,0,0,0,0,0,0, # 58 - 5f
-0,0,0,0,0,0,0,0, # 60 - 67
-0,0,0,0,0,0,0,0, # 68 - 6f
-0,0,0,0,0,0,0,0, # 70 - 77
-0,0,0,0,0,0,0,0, # 78 - 7f
-2,2,2,2,2,2,2,2, # 80 - 87
-2,2,2,2,2,2,2,2, # 88 - 8f
-2,2,2,2,2,2,2,2, # 90 - 97
-2,2,2,2,2,2,2,2, # 98 - 9f
-2,2,2,2,2,2,2,2, # a0 - a7
-2,2,2,2,2,2,2,2, # a8 - af
-2,2,2,2,2,2,2,2, # b0 - b7
-2,2,2,2,2,2,2,2, # b8 - bf
-2,2,2,2,2,2,2,2, # c0 - c7
-2,2,2,2,2,2,2,2, # c8 - cf
-2,2,2,2,2,2,2,2, # d0 - d7
-2,2,2,2,2,2,2,2, # d8 - df
-2,2,2,2,2,2,2,2, # e0 - e7
-2,2,2,2,2,2,2,2, # e8 - ef
-2,2,2,2,2,2,2,2, # f0 - f7
-2,2,2,2,2,2,2,2, # f8 - ff
+ 2, 0, 0, 0, 0, 0, 0, 0, # 00 - 07
+ 0, 0, 0, 0, 0, 0, 2, 2, # 08 - 0f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 10 - 17
+ 0, 0, 0, 1, 0, 0, 0, 0, # 18 - 1f
+ 0, 0, 0, 0, 7, 0, 0, 0, # 20 - 27
+ 3, 0, 0, 0, 0, 0, 0, 0, # 28 - 2f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 30 - 37
+ 0, 0, 0, 0, 0, 0, 0, 0, # 38 - 3f
+ 6, 0, 4, 0, 8, 0, 0, 0, # 40 - 47
+ 0, 9, 5, 0, 0, 0, 0, 0, # 48 - 4f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 50 - 57
+ 0, 0, 0, 0, 0, 0, 0, 0, # 58 - 5f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 60 - 67
+ 0, 0, 0, 0, 0, 0, 0, 0, # 68 - 6f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 70 - 77
+ 0, 0, 0, 0, 0, 0, 0, 0, # 78 - 7f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 80 - 87
+ 2, 2, 2, 2, 2, 2, 2, 2, # 88 - 8f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 90 - 97
+ 2, 2, 2, 2, 2, 2, 2, 2, # 98 - 9f
+ 2, 2, 2, 2, 2, 2, 2, 2, # a0 - a7
+ 2, 2, 2, 2, 2, 2, 2, 2, # a8 - af
+ 2, 2, 2, 2, 2, 2, 2, 2, # b0 - b7
+ 2, 2, 2, 2, 2, 2, 2, 2, # b8 - bf
+ 2, 2, 2, 2, 2, 2, 2, 2, # c0 - c7
+ 2, 2, 2, 2, 2, 2, 2, 2, # c8 - cf
+ 2, 2, 2, 2, 2, 2, 2, 2, # d0 - d7
+ 2, 2, 2, 2, 2, 2, 2, 2, # d8 - df
+ 2, 2, 2, 2, 2, 2, 2, 2, # e0 - e7
+ 2, 2, 2, 2, 2, 2, 2, 2, # e8 - ef
+ 2, 2, 2, 2, 2, 2, 2, 2, # f0 - f7
+ 2, 2, 2, 2, 2, 2, 2, 2, # f8 - ff
)
ISO2022JP_ST = (
-MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 00-07
-MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 08-0f
-MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 10-17
-MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,# 18-1f
-MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 4,MachineState.ERROR,MachineState.ERROR,# 20-27
-MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 6,MachineState.ITS_ME,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,# 28-2f
-MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,# 30-37
-MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 38-3f
-MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,MachineState.START,# 40-47
+ MachineState.START, 3, MachineState.ERROR, MachineState.START, MachineState.START, MachineState.START, MachineState.START, MachineState.START, # 00-07
+ MachineState.START, MachineState.START, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, # 08-0f
+ MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, # 10-17
+ MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ERROR, MachineState.ERROR, # 18-1f
+ MachineState.ERROR, 5, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, 4, MachineState.ERROR, MachineState.ERROR, # 20-27
+ MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, 6, MachineState.ITS_ME, MachineState.ERROR, MachineState.ITS_ME, MachineState.ERROR, # 28-2f
+ MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ITS_ME, # 30-37
+ MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, # 38-3f
+ MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ERROR, MachineState.START, MachineState.START, # 40-47
)
+# fmt: on
ISO2022JP_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
-ISO2022JP_SM_MODEL = {'class_table': ISO2022JP_CLS,
- 'class_factor': 10,
- 'state_table': ISO2022JP_ST,
- 'char_len_table': ISO2022JP_CHAR_LEN_TABLE,
- 'name': "ISO-2022-JP",
- 'language': 'Japanese'}
+ISO2022JP_SM_MODEL: CodingStateMachineDict = {
+ "class_table": ISO2022JP_CLS,
+ "class_factor": 10,
+ "state_table": ISO2022JP_ST,
+ "char_len_table": ISO2022JP_CHAR_LEN_TABLE,
+ "name": "ISO-2022-JP",
+ "language": "Japanese",
+}
+# fmt: off
ISO2022KR_CLS = (
-2,0,0,0,0,0,0,0, # 00 - 07
-0,0,0,0,0,0,0,0, # 08 - 0f
-0,0,0,0,0,0,0,0, # 10 - 17
-0,0,0,1,0,0,0,0, # 18 - 1f
-0,0,0,0,3,0,0,0, # 20 - 27
-0,4,0,0,0,0,0,0, # 28 - 2f
-0,0,0,0,0,0,0,0, # 30 - 37
-0,0,0,0,0,0,0,0, # 38 - 3f
-0,0,0,5,0,0,0,0, # 40 - 47
-0,0,0,0,0,0,0,0, # 48 - 4f
-0,0,0,0,0,0,0,0, # 50 - 57
-0,0,0,0,0,0,0,0, # 58 - 5f
-0,0,0,0,0,0,0,0, # 60 - 67
-0,0,0,0,0,0,0,0, # 68 - 6f
-0,0,0,0,0,0,0,0, # 70 - 77
-0,0,0,0,0,0,0,0, # 78 - 7f
-2,2,2,2,2,2,2,2, # 80 - 87
-2,2,2,2,2,2,2,2, # 88 - 8f
-2,2,2,2,2,2,2,2, # 90 - 97
-2,2,2,2,2,2,2,2, # 98 - 9f
-2,2,2,2,2,2,2,2, # a0 - a7
-2,2,2,2,2,2,2,2, # a8 - af
-2,2,2,2,2,2,2,2, # b0 - b7
-2,2,2,2,2,2,2,2, # b8 - bf
-2,2,2,2,2,2,2,2, # c0 - c7
-2,2,2,2,2,2,2,2, # c8 - cf
-2,2,2,2,2,2,2,2, # d0 - d7
-2,2,2,2,2,2,2,2, # d8 - df
-2,2,2,2,2,2,2,2, # e0 - e7
-2,2,2,2,2,2,2,2, # e8 - ef
-2,2,2,2,2,2,2,2, # f0 - f7
-2,2,2,2,2,2,2,2, # f8 - ff
+ 2, 0, 0, 0, 0, 0, 0, 0, # 00 - 07
+ 0, 0, 0, 0, 0, 0, 0, 0, # 08 - 0f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 10 - 17
+ 0, 0, 0, 1, 0, 0, 0, 0, # 18 - 1f
+ 0, 0, 0, 0, 3, 0, 0, 0, # 20 - 27
+ 0, 4, 0, 0, 0, 0, 0, 0, # 28 - 2f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 30 - 37
+ 0, 0, 0, 0, 0, 0, 0, 0, # 38 - 3f
+ 0, 0, 0, 5, 0, 0, 0, 0, # 40 - 47
+ 0, 0, 0, 0, 0, 0, 0, 0, # 48 - 4f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 50 - 57
+ 0, 0, 0, 0, 0, 0, 0, 0, # 58 - 5f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 60 - 67
+ 0, 0, 0, 0, 0, 0, 0, 0, # 68 - 6f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 70 - 77
+ 0, 0, 0, 0, 0, 0, 0, 0, # 78 - 7f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 80 - 87
+ 2, 2, 2, 2, 2, 2, 2, 2, # 88 - 8f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 90 - 97
+ 2, 2, 2, 2, 2, 2, 2, 2, # 98 - 9f
+ 2, 2, 2, 2, 2, 2, 2, 2, # a0 - a7
+ 2, 2, 2, 2, 2, 2, 2, 2, # a8 - af
+ 2, 2, 2, 2, 2, 2, 2, 2, # b0 - b7
+ 2, 2, 2, 2, 2, 2, 2, 2, # b8 - bf
+ 2, 2, 2, 2, 2, 2, 2, 2, # c0 - c7
+ 2, 2, 2, 2, 2, 2, 2, 2, # c8 - cf
+ 2, 2, 2, 2, 2, 2, 2, 2, # d0 - d7
+ 2, 2, 2, 2, 2, 2, 2, 2, # d8 - df
+ 2, 2, 2, 2, 2, 2, 2, 2, # e0 - e7
+ 2, 2, 2, 2, 2, 2, 2, 2, # e8 - ef
+ 2, 2, 2, 2, 2, 2, 2, 2, # f0 - f7
+ 2, 2, 2, 2, 2, 2, 2, 2, # f8 - ff
)
ISO2022KR_ST = (
-MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,# 00-07
-MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 08-0f
-MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 4,MachineState.ERROR,MachineState.ERROR,# 10-17
-MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 18-1f
-MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 20-27
+ MachineState.START, 3, MachineState.ERROR, MachineState.START, MachineState.START, MachineState.START, MachineState.ERROR, MachineState.ERROR, # 00-07
+ MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ITS_ME, # 08-0f
+ MachineState.ITS_ME, MachineState.ITS_ME, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, 4, MachineState.ERROR, MachineState.ERROR, # 10-17
+ MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, 5, MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, # 18-1f
+ MachineState.ERROR, MachineState.ERROR, MachineState.ERROR, MachineState.ITS_ME, MachineState.START, MachineState.START, MachineState.START, MachineState.START, # 20-27
)
+# fmt: on
ISO2022KR_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0)
-ISO2022KR_SM_MODEL = {'class_table': ISO2022KR_CLS,
- 'class_factor': 6,
- 'state_table': ISO2022KR_ST,
- 'char_len_table': ISO2022KR_CHAR_LEN_TABLE,
- 'name': "ISO-2022-KR",
- 'language': 'Korean'}
-
-
+ISO2022KR_SM_MODEL: CodingStateMachineDict = {
+ "class_table": ISO2022KR_CLS,
+ "class_factor": 6,
+ "state_table": ISO2022KR_ST,
+ "char_len_table": ISO2022KR_CHAR_LEN_TABLE,
+ "name": "ISO-2022-KR",
+ "language": "Korean",
+}
diff --git a/src/pip/_vendor/chardet/eucjpprober.py b/src/pip/_vendor/chardet/eucjpprober.py
index 20ce8f7d1..39487f409 100644
--- a/src/pip/_vendor/chardet/eucjpprober.py
+++ b/src/pip/_vendor/chardet/eucjpprober.py
@@ -25,68 +25,78 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
-from .enums import ProbingState, MachineState
-from .mbcharsetprober import MultiByteCharSetProber
-from .codingstatemachine import CodingStateMachine
+from typing import Union
+
from .chardistribution import EUCJPDistributionAnalysis
+from .codingstatemachine import CodingStateMachine
+from .enums import MachineState, ProbingState
from .jpcntx import EUCJPContextAnalysis
+from .mbcharsetprober import MultiByteCharSetProber
from .mbcssm import EUCJP_SM_MODEL
class EUCJPProber(MultiByteCharSetProber):
- def __init__(self):
- super(EUCJPProber, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
self.coding_sm = CodingStateMachine(EUCJP_SM_MODEL)
self.distribution_analyzer = EUCJPDistributionAnalysis()
self.context_analyzer = EUCJPContextAnalysis()
self.reset()
- def reset(self):
- super(EUCJPProber, self).reset()
+ def reset(self) -> None:
+ super().reset()
self.context_analyzer.reset()
@property
- def charset_name(self):
+ def charset_name(self) -> str:
return "EUC-JP"
@property
- def language(self):
+ def language(self) -> str:
return "Japanese"
- def feed(self, byte_str):
- for i in range(len(byte_str)):
- # PY3K: byte_str is a byte array, so byte_str[i] is an int, not a byte
- coding_state = self.coding_sm.next_state(byte_str[i])
+ def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState:
+ assert self.coding_sm is not None
+ assert self.distribution_analyzer is not None
+
+ for i, byte in enumerate(byte_str):
+ # PY3K: byte_str is a byte array, so byte is an int, not a byte
+ coding_state = self.coding_sm.next_state(byte)
if coding_state == MachineState.ERROR:
- self.logger.debug('%s %s prober hit error at byte %s',
- self.charset_name, self.language, i)
+ self.logger.debug(
+ "%s %s prober hit error at byte %s",
+ self.charset_name,
+ self.language,
+ i,
+ )
self._state = ProbingState.NOT_ME
break
- elif coding_state == MachineState.ITS_ME:
+ if coding_state == MachineState.ITS_ME:
self._state = ProbingState.FOUND_IT
break
- elif coding_state == MachineState.START:
+ if coding_state == MachineState.START:
char_len = self.coding_sm.get_current_charlen()
if i == 0:
- self._last_char[1] = byte_str[0]
+ self._last_char[1] = byte
self.context_analyzer.feed(self._last_char, char_len)
self.distribution_analyzer.feed(self._last_char, char_len)
else:
- self.context_analyzer.feed(byte_str[i - 1:i + 1],
- char_len)
- self.distribution_analyzer.feed(byte_str[i - 1:i + 1],
- char_len)
+ self.context_analyzer.feed(byte_str[i - 1 : i + 1], char_len)
+ self.distribution_analyzer.feed(byte_str[i - 1 : i + 1], char_len)
self._last_char[0] = byte_str[-1]
if self.state == ProbingState.DETECTING:
- if (self.context_analyzer.got_enough_data() and
- (self.get_confidence() > self.SHORTCUT_THRESHOLD)):
+ if self.context_analyzer.got_enough_data() and (
+ self.get_confidence() > self.SHORTCUT_THRESHOLD
+ ):
self._state = ProbingState.FOUND_IT
return self.state
- def get_confidence(self):
+ def get_confidence(self) -> float:
+ assert self.distribution_analyzer is not None
+
context_conf = self.context_analyzer.get_confidence()
distrib_conf = self.distribution_analyzer.get_confidence()
return max(context_conf, distrib_conf)
diff --git a/src/pip/_vendor/chardet/euckrfreq.py b/src/pip/_vendor/chardet/euckrfreq.py
index b68078cb9..7dc3b1038 100644
--- a/src/pip/_vendor/chardet/euckrfreq.py
+++ b/src/pip/_vendor/chardet/euckrfreq.py
@@ -43,6 +43,7 @@ EUCKR_TYPICAL_DISTRIBUTION_RATIO = 6.0
EUCKR_TABLE_SIZE = 2352
# Char to FreqOrder table ,
+# fmt: off
EUCKR_CHAR_TO_FREQ_ORDER = (
13, 130, 120,1396, 481,1719,1720, 328, 609, 212,1721, 707, 400, 299,1722, 87,
1397,1723, 104, 536,1117,1203,1724,1267, 685,1268, 508,1725,1726,1727,1728,1398,
@@ -192,4 +193,4 @@ EUCKR_CHAR_TO_FREQ_ORDER = (
2629,2630,2631, 924, 648, 863, 603,2632,2633, 934,1540, 864, 865,2634, 642,1042,
670,1190,2635,2636,2637,2638, 168,2639, 652, 873, 542,1054,1541,2640,2641,2642, # 512, 256
)
-
+# fmt: on
diff --git a/src/pip/_vendor/chardet/euckrprober.py b/src/pip/_vendor/chardet/euckrprober.py
index 345a060d0..1fc5de046 100644
--- a/src/pip/_vendor/chardet/euckrprober.py
+++ b/src/pip/_vendor/chardet/euckrprober.py
@@ -25,23 +25,23 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
-from .mbcharsetprober import MultiByteCharSetProber
-from .codingstatemachine import CodingStateMachine
from .chardistribution import EUCKRDistributionAnalysis
+from .codingstatemachine import CodingStateMachine
+from .mbcharsetprober import MultiByteCharSetProber
from .mbcssm import EUCKR_SM_MODEL
class EUCKRProber(MultiByteCharSetProber):
- def __init__(self):
- super(EUCKRProber, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
self.coding_sm = CodingStateMachine(EUCKR_SM_MODEL)
self.distribution_analyzer = EUCKRDistributionAnalysis()
self.reset()
@property
- def charset_name(self):
+ def charset_name(self) -> str:
return "EUC-KR"
@property
- def language(self):
+ def language(self) -> str:
return "Korean"
diff --git a/src/pip/_vendor/chardet/euctwfreq.py b/src/pip/_vendor/chardet/euctwfreq.py
index ed7a995a3..4900ccc16 100644
--- a/src/pip/_vendor/chardet/euctwfreq.py
+++ b/src/pip/_vendor/chardet/euctwfreq.py
@@ -43,345 +43,346 @@
EUCTW_TYPICAL_DISTRIBUTION_RATIO = 0.75
-# Char to FreqOrder table ,
+# Char to FreqOrder table
EUCTW_TABLE_SIZE = 5376
+# fmt: off
EUCTW_CHAR_TO_FREQ_ORDER = (
- 1,1800,1506, 255,1431, 198, 9, 82, 6,7310, 177, 202,3615,1256,2808, 110, # 2742
-3735, 33,3241, 261, 76, 44,2113, 16,2931,2184,1176, 659,3868, 26,3404,2643, # 2758
-1198,3869,3313,4060, 410,2211, 302, 590, 361,1963, 8, 204, 58,4296,7311,1931, # 2774
- 63,7312,7313, 317,1614, 75, 222, 159,4061,2412,1480,7314,3500,3068, 224,2809, # 2790
-3616, 3, 10,3870,1471, 29,2774,1135,2852,1939, 873, 130,3242,1123, 312,7315, # 2806
-4297,2051, 507, 252, 682,7316, 142,1914, 124, 206,2932, 34,3501,3173, 64, 604, # 2822
-7317,2494,1976,1977, 155,1990, 645, 641,1606,7318,3405, 337, 72, 406,7319, 80, # 2838
- 630, 238,3174,1509, 263, 939,1092,2644, 756,1440,1094,3406, 449, 69,2969, 591, # 2854
- 179,2095, 471, 115,2034,1843, 60, 50,2970, 134, 806,1868, 734,2035,3407, 180, # 2870
- 995,1607, 156, 537,2893, 688,7320, 319,1305, 779,2144, 514,2374, 298,4298, 359, # 2886
-2495, 90,2707,1338, 663, 11, 906,1099,2545, 20,2436, 182, 532,1716,7321, 732, # 2902
-1376,4062,1311,1420,3175, 25,2312,1056, 113, 399, 382,1949, 242,3408,2467, 529, # 2918
-3243, 475,1447,3617,7322, 117, 21, 656, 810,1297,2295,2329,3502,7323, 126,4063, # 2934
- 706, 456, 150, 613,4299, 71,1118,2036,4064, 145,3069, 85, 835, 486,2114,1246, # 2950
-1426, 428, 727,1285,1015, 800, 106, 623, 303,1281,7324,2127,2354, 347,3736, 221, # 2966
-3503,3110,7325,1955,1153,4065, 83, 296,1199,3070, 192, 624, 93,7326, 822,1897, # 2982
-2810,3111, 795,2064, 991,1554,1542,1592, 27, 43,2853, 859, 139,1456, 860,4300, # 2998
- 437, 712,3871, 164,2392,3112, 695, 211,3017,2096, 195,3872,1608,3504,3505,3618, # 3014
-3873, 234, 811,2971,2097,3874,2229,1441,3506,1615,2375, 668,2076,1638, 305, 228, # 3030
-1664,4301, 467, 415,7327, 262,2098,1593, 239, 108, 300, 200,1033, 512,1247,2077, # 3046
-7328,7329,2173,3176,3619,2673, 593, 845,1062,3244, 88,1723,2037,3875,1950, 212, # 3062
- 266, 152, 149, 468,1898,4066,4302, 77, 187,7330,3018, 37, 5,2972,7331,3876, # 3078
-7332,7333, 39,2517,4303,2894,3177,2078, 55, 148, 74,4304, 545, 483,1474,1029, # 3094
-1665, 217,1869,1531,3113,1104,2645,4067, 24, 172,3507, 900,3877,3508,3509,4305, # 3110
- 32,1408,2811,1312, 329, 487,2355,2247,2708, 784,2674, 4,3019,3314,1427,1788, # 3126
- 188, 109, 499,7334,3620,1717,1789, 888,1217,3020,4306,7335,3510,7336,3315,1520, # 3142
-3621,3878, 196,1034, 775,7337,7338, 929,1815, 249, 439, 38,7339,1063,7340, 794, # 3158
-3879,1435,2296, 46, 178,3245,2065,7341,2376,7342, 214,1709,4307, 804, 35, 707, # 3174
- 324,3622,1601,2546, 140, 459,4068,7343,7344,1365, 839, 272, 978,2257,2572,3409, # 3190
-2128,1363,3623,1423, 697, 100,3071, 48, 70,1231, 495,3114,2193,7345,1294,7346, # 3206
-2079, 462, 586,1042,3246, 853, 256, 988, 185,2377,3410,1698, 434,1084,7347,3411, # 3222
- 314,2615,2775,4308,2330,2331, 569,2280, 637,1816,2518, 757,1162,1878,1616,3412, # 3238
- 287,1577,2115, 768,4309,1671,2854,3511,2519,1321,3737, 909,2413,7348,4069, 933, # 3254
-3738,7349,2052,2356,1222,4310, 765,2414,1322, 786,4311,7350,1919,1462,1677,2895, # 3270
-1699,7351,4312,1424,2437,3115,3624,2590,3316,1774,1940,3413,3880,4070, 309,1369, # 3286
-1130,2812, 364,2230,1653,1299,3881,3512,3882,3883,2646, 525,1085,3021, 902,2000, # 3302
-1475, 964,4313, 421,1844,1415,1057,2281, 940,1364,3116, 376,4314,4315,1381, 7, # 3318
-2520, 983,2378, 336,1710,2675,1845, 321,3414, 559,1131,3022,2742,1808,1132,1313, # 3334
- 265,1481,1857,7352, 352,1203,2813,3247, 167,1089, 420,2814, 776, 792,1724,3513, # 3350
-4071,2438,3248,7353,4072,7354, 446, 229, 333,2743, 901,3739,1200,1557,4316,2647, # 3366
-1920, 395,2744,2676,3740,4073,1835, 125, 916,3178,2616,4317,7355,7356,3741,7357, # 3382
-7358,7359,4318,3117,3625,1133,2547,1757,3415,1510,2313,1409,3514,7360,2145, 438, # 3398
-2591,2896,2379,3317,1068, 958,3023, 461, 311,2855,2677,4074,1915,3179,4075,1978, # 3414
- 383, 750,2745,2617,4076, 274, 539, 385,1278,1442,7361,1154,1964, 384, 561, 210, # 3430
- 98,1295,2548,3515,7362,1711,2415,1482,3416,3884,2897,1257, 129,7363,3742, 642, # 3446
- 523,2776,2777,2648,7364, 141,2231,1333, 68, 176, 441, 876, 907,4077, 603,2592, # 3462
- 710, 171,3417, 404, 549, 18,3118,2393,1410,3626,1666,7365,3516,4319,2898,4320, # 3478
-7366,2973, 368,7367, 146, 366, 99, 871,3627,1543, 748, 807,1586,1185, 22,2258, # 3494
- 379,3743,3180,7368,3181, 505,1941,2618,1991,1382,2314,7369, 380,2357, 218, 702, # 3510
-1817,1248,3418,3024,3517,3318,3249,7370,2974,3628, 930,3250,3744,7371, 59,7372, # 3526
- 585, 601,4078, 497,3419,1112,1314,4321,1801,7373,1223,1472,2174,7374, 749,1836, # 3542
- 690,1899,3745,1772,3885,1476, 429,1043,1790,2232,2116, 917,4079, 447,1086,1629, # 3558
-7375, 556,7376,7377,2020,1654, 844,1090, 105, 550, 966,1758,2815,1008,1782, 686, # 3574
-1095,7378,2282, 793,1602,7379,3518,2593,4322,4080,2933,2297,4323,3746, 980,2496, # 3590
- 544, 353, 527,4324, 908,2678,2899,7380, 381,2619,1942,1348,7381,1341,1252, 560, # 3606
-3072,7382,3420,2856,7383,2053, 973, 886,2080, 143,4325,7384,7385, 157,3886, 496, # 3622
-4081, 57, 840, 540,2038,4326,4327,3421,2117,1445, 970,2259,1748,1965,2081,4082, # 3638
-3119,1234,1775,3251,2816,3629, 773,1206,2129,1066,2039,1326,3887,1738,1725,4083, # 3654
- 279,3120, 51,1544,2594, 423,1578,2130,2066, 173,4328,1879,7386,7387,1583, 264, # 3670
- 610,3630,4329,2439, 280, 154,7388,7389,7390,1739, 338,1282,3073, 693,2857,1411, # 3686
-1074,3747,2440,7391,4330,7392,7393,1240, 952,2394,7394,2900,1538,2679, 685,1483, # 3702
-4084,2468,1436, 953,4085,2054,4331, 671,2395, 79,4086,2441,3252, 608, 567,2680, # 3718
-3422,4087,4088,1691, 393,1261,1791,2396,7395,4332,7396,7397,7398,7399,1383,1672, # 3734
-3748,3182,1464, 522,1119, 661,1150, 216, 675,4333,3888,1432,3519, 609,4334,2681, # 3750
-2397,7400,7401,7402,4089,3025, 0,7403,2469, 315, 231,2442, 301,3319,4335,2380, # 3766
-7404, 233,4090,3631,1818,4336,4337,7405, 96,1776,1315,2082,7406, 257,7407,1809, # 3782
-3632,2709,1139,1819,4091,2021,1124,2163,2778,1777,2649,7408,3074, 363,1655,3183, # 3798
-7409,2975,7410,7411,7412,3889,1567,3890, 718, 103,3184, 849,1443, 341,3320,2934, # 3814
-1484,7413,1712, 127, 67, 339,4092,2398, 679,1412, 821,7414,7415, 834, 738, 351, # 3830
-2976,2146, 846, 235,1497,1880, 418,1992,3749,2710, 186,1100,2147,2746,3520,1545, # 3846
-1355,2935,2858,1377, 583,3891,4093,2573,2977,7416,1298,3633,1078,2549,3634,2358, # 3862
- 78,3750,3751, 267,1289,2099,2001,1594,4094, 348, 369,1274,2194,2175,1837,4338, # 3878
-1820,2817,3635,2747,2283,2002,4339,2936,2748, 144,3321, 882,4340,3892,2749,3423, # 3894
-4341,2901,7417,4095,1726, 320,7418,3893,3026, 788,2978,7419,2818,1773,1327,2859, # 3910
-3894,2819,7420,1306,4342,2003,1700,3752,3521,2359,2650, 787,2022, 506, 824,3636, # 3926
- 534, 323,4343,1044,3322,2023,1900, 946,3424,7421,1778,1500,1678,7422,1881,4344, # 3942
- 165, 243,4345,3637,2521, 123, 683,4096, 764,4346, 36,3895,1792, 589,2902, 816, # 3958
- 626,1667,3027,2233,1639,1555,1622,3753,3896,7423,3897,2860,1370,1228,1932, 891, # 3974
-2083,2903, 304,4097,7424, 292,2979,2711,3522, 691,2100,4098,1115,4347, 118, 662, # 3990
-7425, 611,1156, 854,2381,1316,2861, 2, 386, 515,2904,7426,7427,3253, 868,2234, # 4006
-1486, 855,2651, 785,2212,3028,7428,1040,3185,3523,7429,3121, 448,7430,1525,7431, # 4022
-2164,4348,7432,3754,7433,4099,2820,3524,3122, 503, 818,3898,3123,1568, 814, 676, # 4038
-1444, 306,1749,7434,3755,1416,1030, 197,1428, 805,2821,1501,4349,7435,7436,7437, # 4054
-1993,7438,4350,7439,7440,2195, 13,2779,3638,2980,3124,1229,1916,7441,3756,2131, # 4070
-7442,4100,4351,2399,3525,7443,2213,1511,1727,1120,7444,7445, 646,3757,2443, 307, # 4086
-7446,7447,1595,3186,7448,7449,7450,3639,1113,1356,3899,1465,2522,2523,7451, 519, # 4102
-7452, 128,2132, 92,2284,1979,7453,3900,1512, 342,3125,2196,7454,2780,2214,1980, # 4118
-3323,7455, 290,1656,1317, 789, 827,2360,7456,3758,4352, 562, 581,3901,7457, 401, # 4134
-4353,2248, 94,4354,1399,2781,7458,1463,2024,4355,3187,1943,7459, 828,1105,4101, # 4150
-1262,1394,7460,4102, 605,4356,7461,1783,2862,7462,2822, 819,2101, 578,2197,2937, # 4166
-7463,1502, 436,3254,4103,3255,2823,3902,2905,3425,3426,7464,2712,2315,7465,7466, # 4182
-2332,2067, 23,4357, 193, 826,3759,2102, 699,1630,4104,3075, 390,1793,1064,3526, # 4198
-7467,1579,3076,3077,1400,7468,4105,1838,1640,2863,7469,4358,4359, 137,4106, 598, # 4214
-3078,1966, 780, 104, 974,2938,7470, 278, 899, 253, 402, 572, 504, 493,1339,7471, # 4230
-3903,1275,4360,2574,2550,7472,3640,3029,3079,2249, 565,1334,2713, 863, 41,7473, # 4246
-7474,4361,7475,1657,2333, 19, 463,2750,4107, 606,7476,2981,3256,1087,2084,1323, # 4262
-2652,2982,7477,1631,1623,1750,4108,2682,7478,2864, 791,2714,2653,2334, 232,2416, # 4278
-7479,2983,1498,7480,2654,2620, 755,1366,3641,3257,3126,2025,1609, 119,1917,3427, # 4294
- 862,1026,4109,7481,3904,3760,4362,3905,4363,2260,1951,2470,7482,1125, 817,4110, # 4310
-4111,3906,1513,1766,2040,1487,4112,3030,3258,2824,3761,3127,7483,7484,1507,7485, # 4326
-2683, 733, 40,1632,1106,2865, 345,4113, 841,2524, 230,4364,2984,1846,3259,3428, # 4342
-7486,1263, 986,3429,7487, 735, 879, 254,1137, 857, 622,1300,1180,1388,1562,3907, # 4358
-3908,2939, 967,2751,2655,1349, 592,2133,1692,3324,2985,1994,4114,1679,3909,1901, # 4374
-2185,7488, 739,3642,2715,1296,1290,7489,4115,2198,2199,1921,1563,2595,2551,1870, # 4390
-2752,2986,7490, 435,7491, 343,1108, 596, 17,1751,4365,2235,3430,3643,7492,4366, # 4406
- 294,3527,2940,1693, 477, 979, 281,2041,3528, 643,2042,3644,2621,2782,2261,1031, # 4422
-2335,2134,2298,3529,4367, 367,1249,2552,7493,3530,7494,4368,1283,3325,2004, 240, # 4438
-1762,3326,4369,4370, 836,1069,3128, 474,7495,2148,2525, 268,3531,7496,3188,1521, # 4454
-1284,7497,1658,1546,4116,7498,3532,3533,7499,4117,3327,2684,1685,4118, 961,1673, # 4470
-2622, 190,2005,2200,3762,4371,4372,7500, 570,2497,3645,1490,7501,4373,2623,3260, # 4486
-1956,4374, 584,1514, 396,1045,1944,7502,4375,1967,2444,7503,7504,4376,3910, 619, # 4502
-7505,3129,3261, 215,2006,2783,2553,3189,4377,3190,4378, 763,4119,3763,4379,7506, # 4518
-7507,1957,1767,2941,3328,3646,1174, 452,1477,4380,3329,3130,7508,2825,1253,2382, # 4534
-2186,1091,2285,4120, 492,7509, 638,1169,1824,2135,1752,3911, 648, 926,1021,1324, # 4550
-4381, 520,4382, 997, 847,1007, 892,4383,3764,2262,1871,3647,7510,2400,1784,4384, # 4566
-1952,2942,3080,3191,1728,4121,2043,3648,4385,2007,1701,3131,1551, 30,2263,4122, # 4582
-7511,2026,4386,3534,7512, 501,7513,4123, 594,3431,2165,1821,3535,3432,3536,3192, # 4598
- 829,2826,4124,7514,1680,3132,1225,4125,7515,3262,4387,4126,3133,2336,7516,4388, # 4614
-4127,7517,3912,3913,7518,1847,2383,2596,3330,7519,4389, 374,3914, 652,4128,4129, # 4630
- 375,1140, 798,7520,7521,7522,2361,4390,2264, 546,1659, 138,3031,2445,4391,7523, # 4646
-2250, 612,1848, 910, 796,3765,1740,1371, 825,3766,3767,7524,2906,2554,7525, 692, # 4662
- 444,3032,2624, 801,4392,4130,7526,1491, 244,1053,3033,4131,4132, 340,7527,3915, # 4678
-1041,2987, 293,1168, 87,1357,7528,1539, 959,7529,2236, 721, 694,4133,3768, 219, # 4694
-1478, 644,1417,3331,2656,1413,1401,1335,1389,3916,7530,7531,2988,2362,3134,1825, # 4710
- 730,1515, 184,2827, 66,4393,7532,1660,2943, 246,3332, 378,1457, 226,3433, 975, # 4726
-3917,2944,1264,3537, 674, 696,7533, 163,7534,1141,2417,2166, 713,3538,3333,4394, # 4742
-3918,7535,7536,1186, 15,7537,1079,1070,7538,1522,3193,3539, 276,1050,2716, 758, # 4758
-1126, 653,2945,3263,7539,2337, 889,3540,3919,3081,2989, 903,1250,4395,3920,3434, # 4774
-3541,1342,1681,1718, 766,3264, 286, 89,2946,3649,7540,1713,7541,2597,3334,2990, # 4790
-7542,2947,2215,3194,2866,7543,4396,2498,2526, 181, 387,1075,3921, 731,2187,3335, # 4806
-7544,3265, 310, 313,3435,2299, 770,4134, 54,3034, 189,4397,3082,3769,3922,7545, # 4822
-1230,1617,1849, 355,3542,4135,4398,3336, 111,4136,3650,1350,3135,3436,3035,4137, # 4838
-2149,3266,3543,7546,2784,3923,3924,2991, 722,2008,7547,1071, 247,1207,2338,2471, # 4854
-1378,4399,2009, 864,1437,1214,4400, 373,3770,1142,2216, 667,4401, 442,2753,2555, # 4870
-3771,3925,1968,4138,3267,1839, 837, 170,1107, 934,1336,1882,7548,7549,2118,4139, # 4886
-2828, 743,1569,7550,4402,4140, 582,2384,1418,3437,7551,1802,7552, 357,1395,1729, # 4902
-3651,3268,2418,1564,2237,7553,3083,3772,1633,4403,1114,2085,4141,1532,7554, 482, # 4918
-2446,4404,7555,7556,1492, 833,1466,7557,2717,3544,1641,2829,7558,1526,1272,3652, # 4934
-4142,1686,1794, 416,2556,1902,1953,1803,7559,3773,2785,3774,1159,2316,7560,2867, # 4950
-4405,1610,1584,3036,2419,2754, 443,3269,1163,3136,7561,7562,3926,7563,4143,2499, # 4966
-3037,4406,3927,3137,2103,1647,3545,2010,1872,4144,7564,4145, 431,3438,7565, 250, # 4982
- 97, 81,4146,7566,1648,1850,1558, 160, 848,7567, 866, 740,1694,7568,2201,2830, # 4998
-3195,4147,4407,3653,1687, 950,2472, 426, 469,3196,3654,3655,3928,7569,7570,1188, # 5014
- 424,1995, 861,3546,4148,3775,2202,2685, 168,1235,3547,4149,7571,2086,1674,4408, # 5030
-3337,3270, 220,2557,1009,7572,3776, 670,2992, 332,1208, 717,7573,7574,3548,2447, # 5046
-3929,3338,7575, 513,7576,1209,2868,3339,3138,4409,1080,7577,7578,7579,7580,2527, # 5062
-3656,3549, 815,1587,3930,3931,7581,3550,3439,3777,1254,4410,1328,3038,1390,3932, # 5078
-1741,3933,3778,3934,7582, 236,3779,2448,3271,7583,7584,3657,3780,1273,3781,4411, # 5094
-7585, 308,7586,4412, 245,4413,1851,2473,1307,2575, 430, 715,2136,2449,7587, 270, # 5110
- 199,2869,3935,7588,3551,2718,1753, 761,1754, 725,1661,1840,4414,3440,3658,7589, # 5126
-7590, 587, 14,3272, 227,2598, 326, 480,2265, 943,2755,3552, 291, 650,1883,7591, # 5142
-1702,1226, 102,1547, 62,3441, 904,4415,3442,1164,4150,7592,7593,1224,1548,2756, # 5158
- 391, 498,1493,7594,1386,1419,7595,2055,1177,4416, 813, 880,1081,2363, 566,1145, # 5174
-4417,2286,1001,1035,2558,2599,2238, 394,1286,7596,7597,2068,7598, 86,1494,1730, # 5190
-3936, 491,1588, 745, 897,2948, 843,3340,3937,2757,2870,3273,1768, 998,2217,2069, # 5206
- 397,1826,1195,1969,3659,2993,3341, 284,7599,3782,2500,2137,2119,1903,7600,3938, # 5222
-2150,3939,4151,1036,3443,1904, 114,2559,4152, 209,1527,7601,7602,2949,2831,2625, # 5238
-2385,2719,3139, 812,2560,7603,3274,7604,1559, 737,1884,3660,1210, 885, 28,2686, # 5254
-3553,3783,7605,4153,1004,1779,4418,7606, 346,1981,2218,2687,4419,3784,1742, 797, # 5270
-1642,3940,1933,1072,1384,2151, 896,3941,3275,3661,3197,2871,3554,7607,2561,1958, # 5286
-4420,2450,1785,7608,7609,7610,3942,4154,1005,1308,3662,4155,2720,4421,4422,1528, # 5302
-2600, 161,1178,4156,1982, 987,4423,1101,4157, 631,3943,1157,3198,2420,1343,1241, # 5318
-1016,2239,2562, 372, 877,2339,2501,1160, 555,1934, 911,3944,7611, 466,1170, 169, # 5334
-1051,2907,2688,3663,2474,2994,1182,2011,2563,1251,2626,7612, 992,2340,3444,1540, # 5350
-2721,1201,2070,2401,1996,2475,7613,4424, 528,1922,2188,1503,1873,1570,2364,3342, # 5366
-3276,7614, 557,1073,7615,1827,3445,2087,2266,3140,3039,3084, 767,3085,2786,4425, # 5382
-1006,4158,4426,2341,1267,2176,3664,3199, 778,3945,3200,2722,1597,2657,7616,4427, # 5398
-7617,3446,7618,7619,7620,3277,2689,1433,3278, 131, 95,1504,3946, 723,4159,3141, # 5414
-1841,3555,2758,2189,3947,2027,2104,3665,7621,2995,3948,1218,7622,3343,3201,3949, # 5430
-4160,2576, 248,1634,3785, 912,7623,2832,3666,3040,3786, 654, 53,7624,2996,7625, # 5446
-1688,4428, 777,3447,1032,3950,1425,7626, 191, 820,2120,2833, 971,4429, 931,3202, # 5462
- 135, 664, 783,3787,1997, 772,2908,1935,3951,3788,4430,2909,3203, 282,2723, 640, # 5478
-1372,3448,1127, 922, 325,3344,7627,7628, 711,2044,7629,7630,3952,2219,2787,1936, # 5494
-3953,3345,2220,2251,3789,2300,7631,4431,3790,1258,3279,3954,3204,2138,2950,3955, # 5510
-3956,7632,2221, 258,3205,4432, 101,1227,7633,3280,1755,7634,1391,3281,7635,2910, # 5526
-2056, 893,7636,7637,7638,1402,4161,2342,7639,7640,3206,3556,7641,7642, 878,1325, # 5542
-1780,2788,4433, 259,1385,2577, 744,1183,2267,4434,7643,3957,2502,7644, 684,1024, # 5558
-4162,7645, 472,3557,3449,1165,3282,3958,3959, 322,2152, 881, 455,1695,1152,1340, # 5574
- 660, 554,2153,4435,1058,4436,4163, 830,1065,3346,3960,4437,1923,7646,1703,1918, # 5590
-7647, 932,2268, 122,7648,4438, 947, 677,7649,3791,2627, 297,1905,1924,2269,4439, # 5606
-2317,3283,7650,7651,4164,7652,4165, 84,4166, 112, 989,7653, 547,1059,3961, 701, # 5622
-3558,1019,7654,4167,7655,3450, 942, 639, 457,2301,2451, 993,2951, 407, 851, 494, # 5638
-4440,3347, 927,7656,1237,7657,2421,3348, 573,4168, 680, 921,2911,1279,1874, 285, # 5654
- 790,1448,1983, 719,2167,7658,7659,4441,3962,3963,1649,7660,1541, 563,7661,1077, # 5670
-7662,3349,3041,3451, 511,2997,3964,3965,3667,3966,1268,2564,3350,3207,4442,4443, # 5686
-7663, 535,1048,1276,1189,2912,2028,3142,1438,1373,2834,2952,1134,2012,7664,4169, # 5702
-1238,2578,3086,1259,7665, 700,7666,2953,3143,3668,4170,7667,4171,1146,1875,1906, # 5718
-4444,2601,3967, 781,2422, 132,1589, 203, 147, 273,2789,2402, 898,1786,2154,3968, # 5734
-3969,7668,3792,2790,7669,7670,4445,4446,7671,3208,7672,1635,3793, 965,7673,1804, # 5750
-2690,1516,3559,1121,1082,1329,3284,3970,1449,3794, 65,1128,2835,2913,2759,1590, # 5766
-3795,7674,7675, 12,2658, 45, 976,2579,3144,4447, 517,2528,1013,1037,3209,7676, # 5782
-3796,2836,7677,3797,7678,3452,7679,2602, 614,1998,2318,3798,3087,2724,2628,7680, # 5798
-2580,4172, 599,1269,7681,1810,3669,7682,2691,3088, 759,1060, 489,1805,3351,3285, # 5814
-1358,7683,7684,2386,1387,1215,2629,2252, 490,7685,7686,4173,1759,2387,2343,7687, # 5830
-4448,3799,1907,3971,2630,1806,3210,4449,3453,3286,2760,2344, 874,7688,7689,3454, # 5846
-3670,1858, 91,2914,3671,3042,3800,4450,7690,3145,3972,2659,7691,3455,1202,1403, # 5862
-3801,2954,2529,1517,2503,4451,3456,2504,7692,4452,7693,2692,1885,1495,1731,3973, # 5878
-2365,4453,7694,2029,7695,7696,3974,2693,1216, 237,2581,4174,2319,3975,3802,4454, # 5894
-4455,2694,3560,3457, 445,4456,7697,7698,7699,7700,2761, 61,3976,3672,1822,3977, # 5910
-7701, 687,2045, 935, 925, 405,2660, 703,1096,1859,2725,4457,3978,1876,1367,2695, # 5926
-3352, 918,2105,1781,2476, 334,3287,1611,1093,4458, 564,3146,3458,3673,3353, 945, # 5942
-2631,2057,4459,7702,1925, 872,4175,7703,3459,2696,3089, 349,4176,3674,3979,4460, # 5958
-3803,4177,3675,2155,3980,4461,4462,4178,4463,2403,2046, 782,3981, 400, 251,4179, # 5974
-1624,7704,7705, 277,3676, 299,1265, 476,1191,3804,2121,4180,4181,1109, 205,7706, # 5990
-2582,1000,2156,3561,1860,7707,7708,7709,4464,7710,4465,2565, 107,2477,2157,3982, # 6006
-3460,3147,7711,1533, 541,1301, 158, 753,4182,2872,3562,7712,1696, 370,1088,4183, # 6022
-4466,3563, 579, 327, 440, 162,2240, 269,1937,1374,3461, 968,3043, 56,1396,3090, # 6038
-2106,3288,3354,7713,1926,2158,4467,2998,7714,3564,7715,7716,3677,4468,2478,7717, # 6054
-2791,7718,1650,4469,7719,2603,7720,7721,3983,2661,3355,1149,3356,3984,3805,3985, # 6070
-7722,1076, 49,7723, 951,3211,3289,3290, 450,2837, 920,7724,1811,2792,2366,4184, # 6086
-1908,1138,2367,3806,3462,7725,3212,4470,1909,1147,1518,2423,4471,3807,7726,4472, # 6102
-2388,2604, 260,1795,3213,7727,7728,3808,3291, 708,7729,3565,1704,7730,3566,1351, # 6118
-1618,3357,2999,1886, 944,4185,3358,4186,3044,3359,4187,7731,3678, 422, 413,1714, # 6134
-3292, 500,2058,2345,4188,2479,7732,1344,1910, 954,7733,1668,7734,7735,3986,2404, # 6150
-4189,3567,3809,4190,7736,2302,1318,2505,3091, 133,3092,2873,4473, 629, 31,2838, # 6166
-2697,3810,4474, 850, 949,4475,3987,2955,1732,2088,4191,1496,1852,7737,3988, 620, # 6182
-3214, 981,1242,3679,3360,1619,3680,1643,3293,2139,2452,1970,1719,3463,2168,7738, # 6198
-3215,7739,7740,3361,1828,7741,1277,4476,1565,2047,7742,1636,3568,3093,7743, 869, # 6214
-2839, 655,3811,3812,3094,3989,3000,3813,1310,3569,4477,7744,7745,7746,1733, 558, # 6230
-4478,3681, 335,1549,3045,1756,4192,3682,1945,3464,1829,1291,1192, 470,2726,2107, # 6246
-2793, 913,1054,3990,7747,1027,7748,3046,3991,4479, 982,2662,3362,3148,3465,3216, # 6262
-3217,1946,2794,7749, 571,4480,7750,1830,7751,3570,2583,1523,2424,7752,2089, 984, # 6278
-4481,3683,1959,7753,3684, 852, 923,2795,3466,3685, 969,1519, 999,2048,2320,1705, # 6294
-7754,3095, 615,1662, 151, 597,3992,2405,2321,1049, 275,4482,3686,4193, 568,3687, # 6310
-3571,2480,4194,3688,7755,2425,2270, 409,3218,7756,1566,2874,3467,1002, 769,2840, # 6326
- 194,2090,3149,3689,2222,3294,4195, 628,1505,7757,7758,1763,2177,3001,3993, 521, # 6342
-1161,2584,1787,2203,2406,4483,3994,1625,4196,4197, 412, 42,3096, 464,7759,2632, # 6358
-4484,3363,1760,1571,2875,3468,2530,1219,2204,3814,2633,2140,2368,4485,4486,3295, # 6374
-1651,3364,3572,7760,7761,3573,2481,3469,7762,3690,7763,7764,2271,2091, 460,7765, # 6390
-4487,7766,3002, 962, 588,3574, 289,3219,2634,1116, 52,7767,3047,1796,7768,7769, # 6406
-7770,1467,7771,1598,1143,3691,4198,1984,1734,1067,4488,1280,3365, 465,4489,1572, # 6422
- 510,7772,1927,2241,1812,1644,3575,7773,4490,3692,7774,7775,2663,1573,1534,7776, # 6438
-7777,4199, 536,1807,1761,3470,3815,3150,2635,7778,7779,7780,4491,3471,2915,1911, # 6454
-2796,7781,3296,1122, 377,3220,7782, 360,7783,7784,4200,1529, 551,7785,2059,3693, # 6470
-1769,2426,7786,2916,4201,3297,3097,2322,2108,2030,4492,1404, 136,1468,1479, 672, # 6486
-1171,3221,2303, 271,3151,7787,2762,7788,2049, 678,2727, 865,1947,4493,7789,2013, # 6502
-3995,2956,7790,2728,2223,1397,3048,3694,4494,4495,1735,2917,3366,3576,7791,3816, # 6518
- 509,2841,2453,2876,3817,7792,7793,3152,3153,4496,4202,2531,4497,2304,1166,1010, # 6534
- 552, 681,1887,7794,7795,2957,2958,3996,1287,1596,1861,3154, 358, 453, 736, 175, # 6550
- 478,1117, 905,1167,1097,7796,1853,1530,7797,1706,7798,2178,3472,2287,3695,3473, # 6566
-3577,4203,2092,4204,7799,3367,1193,2482,4205,1458,2190,2205,1862,1888,1421,3298, # 6582
-2918,3049,2179,3474, 595,2122,7800,3997,7801,7802,4206,1707,2636, 223,3696,1359, # 6598
- 751,3098, 183,3475,7803,2797,3003, 419,2369, 633, 704,3818,2389, 241,7804,7805, # 6614
-7806, 838,3004,3697,2272,2763,2454,3819,1938,2050,3998,1309,3099,2242,1181,7807, # 6630
-1136,2206,3820,2370,1446,4207,2305,4498,7808,7809,4208,1055,2605, 484,3698,7810, # 6646
-3999, 625,4209,2273,3368,1499,4210,4000,7811,4001,4211,3222,2274,2275,3476,7812, # 6662
-7813,2764, 808,2606,3699,3369,4002,4212,3100,2532, 526,3370,3821,4213, 955,7814, # 6678
-1620,4214,2637,2427,7815,1429,3700,1669,1831, 994, 928,7816,3578,1260,7817,7818, # 6694
-7819,1948,2288, 741,2919,1626,4215,2729,2455, 867,1184, 362,3371,1392,7820,7821, # 6710
-4003,4216,1770,1736,3223,2920,4499,4500,1928,2698,1459,1158,7822,3050,3372,2877, # 6726
-1292,1929,2506,2842,3701,1985,1187,2071,2014,2607,4217,7823,2566,2507,2169,3702, # 6742
-2483,3299,7824,3703,4501,7825,7826, 666,1003,3005,1022,3579,4218,7827,4502,1813, # 6758
-2253, 574,3822,1603, 295,1535, 705,3823,4219, 283, 858, 417,7828,7829,3224,4503, # 6774
-4504,3051,1220,1889,1046,2276,2456,4004,1393,1599, 689,2567, 388,4220,7830,2484, # 6790
- 802,7831,2798,3824,2060,1405,2254,7832,4505,3825,2109,1052,1345,3225,1585,7833, # 6806
- 809,7834,7835,7836, 575,2730,3477, 956,1552,1469,1144,2323,7837,2324,1560,2457, # 6822
-3580,3226,4005, 616,2207,3155,2180,2289,7838,1832,7839,3478,4506,7840,1319,3704, # 6838
-3705,1211,3581,1023,3227,1293,2799,7841,7842,7843,3826, 607,2306,3827, 762,2878, # 6854
-1439,4221,1360,7844,1485,3052,7845,4507,1038,4222,1450,2061,2638,4223,1379,4508, # 6870
-2585,7846,7847,4224,1352,1414,2325,2921,1172,7848,7849,3828,3829,7850,1797,1451, # 6886
-7851,7852,7853,7854,2922,4006,4007,2485,2346, 411,4008,4009,3582,3300,3101,4509, # 6902
-1561,2664,1452,4010,1375,7855,7856, 47,2959, 316,7857,1406,1591,2923,3156,7858, # 6918
-1025,2141,3102,3157, 354,2731, 884,2224,4225,2407, 508,3706, 726,3583, 996,2428, # 6934
-3584, 729,7859, 392,2191,1453,4011,4510,3707,7860,7861,2458,3585,2608,1675,2800, # 6950
- 919,2347,2960,2348,1270,4511,4012, 73,7862,7863, 647,7864,3228,2843,2255,1550, # 6966
-1346,3006,7865,1332, 883,3479,7866,7867,7868,7869,3301,2765,7870,1212, 831,1347, # 6982
-4226,4512,2326,3830,1863,3053, 720,3831,4513,4514,3832,7871,4227,7872,7873,4515, # 6998
-7874,7875,1798,4516,3708,2609,4517,3586,1645,2371,7876,7877,2924, 669,2208,2665, # 7014
-2429,7878,2879,7879,7880,1028,3229,7881,4228,2408,7882,2256,1353,7883,7884,4518, # 7030
-3158, 518,7885,4013,7886,4229,1960,7887,2142,4230,7888,7889,3007,2349,2350,3833, # 7046
- 516,1833,1454,4014,2699,4231,4519,2225,2610,1971,1129,3587,7890,2766,7891,2961, # 7062
-1422, 577,1470,3008,1524,3373,7892,7893, 432,4232,3054,3480,7894,2586,1455,2508, # 7078
-2226,1972,1175,7895,1020,2732,4015,3481,4520,7896,2733,7897,1743,1361,3055,3482, # 7094
-2639,4016,4233,4521,2290, 895, 924,4234,2170, 331,2243,3056, 166,1627,3057,1098, # 7110
-7898,1232,2880,2227,3374,4522, 657, 403,1196,2372, 542,3709,3375,1600,4235,3483, # 7126
-7899,4523,2767,3230, 576, 530,1362,7900,4524,2533,2666,3710,4017,7901, 842,3834, # 7142
-7902,2801,2031,1014,4018, 213,2700,3376, 665, 621,4236,7903,3711,2925,2430,7904, # 7158
-2431,3302,3588,3377,7905,4237,2534,4238,4525,3589,1682,4239,3484,1380,7906, 724, # 7174
-2277, 600,1670,7907,1337,1233,4526,3103,2244,7908,1621,4527,7909, 651,4240,7910, # 7190
-1612,4241,2611,7911,2844,7912,2734,2307,3058,7913, 716,2459,3059, 174,1255,2701, # 7206
-4019,3590, 548,1320,1398, 728,4020,1574,7914,1890,1197,3060,4021,7915,3061,3062, # 7222
-3712,3591,3713, 747,7916, 635,4242,4528,7917,7918,7919,4243,7920,7921,4529,7922, # 7238
-3378,4530,2432, 451,7923,3714,2535,2072,4244,2735,4245,4022,7924,1764,4531,7925, # 7254
-4246, 350,7926,2278,2390,2486,7927,4247,4023,2245,1434,4024, 488,4532, 458,4248, # 7270
-4025,3715, 771,1330,2391,3835,2568,3159,2159,2409,1553,2667,3160,4249,7928,2487, # 7286
-2881,2612,1720,2702,4250,3379,4533,7929,2536,4251,7930,3231,4252,2768,7931,2015, # 7302
-2736,7932,1155,1017,3716,3836,7933,3303,2308, 201,1864,4253,1430,7934,4026,7935, # 7318
-7936,7937,7938,7939,4254,1604,7940, 414,1865, 371,2587,4534,4535,3485,2016,3104, # 7334
-4536,1708, 960,4255, 887, 389,2171,1536,1663,1721,7941,2228,4027,2351,2926,1580, # 7350
-7942,7943,7944,1744,7945,2537,4537,4538,7946,4539,7947,2073,7948,7949,3592,3380, # 7366
-2882,4256,7950,4257,2640,3381,2802, 673,2703,2460, 709,3486,4028,3593,4258,7951, # 7382
-1148, 502, 634,7952,7953,1204,4540,3594,1575,4541,2613,3717,7954,3718,3105, 948, # 7398
-3232, 121,1745,3837,1110,7955,4259,3063,2509,3009,4029,3719,1151,1771,3838,1488, # 7414
-4030,1986,7956,2433,3487,7957,7958,2093,7959,4260,3839,1213,1407,2803, 531,2737, # 7430
-2538,3233,1011,1537,7960,2769,4261,3106,1061,7961,3720,3721,1866,2883,7962,2017, # 7446
- 120,4262,4263,2062,3595,3234,2309,3840,2668,3382,1954,4542,7963,7964,3488,1047, # 7462
-2704,1266,7965,1368,4543,2845, 649,3383,3841,2539,2738,1102,2846,2669,7966,7967, # 7478
-1999,7968,1111,3596,2962,7969,2488,3842,3597,2804,1854,3384,3722,7970,7971,3385, # 7494
-2410,2884,3304,3235,3598,7972,2569,7973,3599,2805,4031,1460, 856,7974,3600,7975, # 7510
-2885,2963,7976,2886,3843,7977,4264, 632,2510, 875,3844,1697,3845,2291,7978,7979, # 7526
-4544,3010,1239, 580,4545,4265,7980, 914, 936,2074,1190,4032,1039,2123,7981,7982, # 7542
-7983,3386,1473,7984,1354,4266,3846,7985,2172,3064,4033, 915,3305,4267,4268,3306, # 7558
-1605,1834,7986,2739, 398,3601,4269,3847,4034, 328,1912,2847,4035,3848,1331,4270, # 7574
-3011, 937,4271,7987,3602,4036,4037,3387,2160,4546,3388, 524, 742, 538,3065,1012, # 7590
-7988,7989,3849,2461,7990, 658,1103, 225,3850,7991,7992,4547,7993,4548,7994,3236, # 7606
-1243,7995,4038, 963,2246,4549,7996,2705,3603,3161,7997,7998,2588,2327,7999,4550, # 7622
-8000,8001,8002,3489,3307, 957,3389,2540,2032,1930,2927,2462, 870,2018,3604,1746, # 7638
-2770,2771,2434,2463,8003,3851,8004,3723,3107,3724,3490,3390,3725,8005,1179,3066, # 7654
-8006,3162,2373,4272,3726,2541,3163,3108,2740,4039,8007,3391,1556,2542,2292, 977, # 7670
-2887,2033,4040,1205,3392,8008,1765,3393,3164,2124,1271,1689, 714,4551,3491,8009, # 7686
-2328,3852, 533,4273,3605,2181, 617,8010,2464,3308,3492,2310,8011,8012,3165,8013, # 7702
-8014,3853,1987, 618, 427,2641,3493,3394,8015,8016,1244,1690,8017,2806,4274,4552, # 7718
-8018,3494,8019,8020,2279,1576, 473,3606,4275,3395, 972,8021,3607,8022,3067,8023, # 7734
-8024,4553,4554,8025,3727,4041,4042,8026, 153,4555, 356,8027,1891,2888,4276,2143, # 7750
- 408, 803,2352,8028,3854,8029,4277,1646,2570,2511,4556,4557,3855,8030,3856,4278, # 7766
-8031,2411,3396, 752,8032,8033,1961,2964,8034, 746,3012,2465,8035,4279,3728, 698, # 7782
-4558,1892,4280,3608,2543,4559,3609,3857,8036,3166,3397,8037,1823,1302,4043,2706, # 7798
-3858,1973,4281,8038,4282,3167, 823,1303,1288,1236,2848,3495,4044,3398, 774,3859, # 7814
-8039,1581,4560,1304,2849,3860,4561,8040,2435,2161,1083,3237,4283,4045,4284, 344, # 7830
-1173, 288,2311, 454,1683,8041,8042,1461,4562,4046,2589,8043,8044,4563, 985, 894, # 7846
-8045,3399,3168,8046,1913,2928,3729,1988,8047,2110,1974,8048,4047,8049,2571,1194, # 7862
- 425,8050,4564,3169,1245,3730,4285,8051,8052,2850,8053, 636,4565,1855,3861, 760, # 7878
-1799,8054,4286,2209,1508,4566,4048,1893,1684,2293,8055,8056,8057,4287,4288,2210, # 7894
- 479,8058,8059, 832,8060,4049,2489,8061,2965,2490,3731, 990,3109, 627,1814,2642, # 7910
-4289,1582,4290,2125,2111,3496,4567,8062, 799,4291,3170,8063,4568,2112,1737,3013, # 7926
-1018, 543, 754,4292,3309,1676,4569,4570,4050,8064,1489,8065,3497,8066,2614,2889, # 7942
-4051,8067,8068,2966,8069,8070,8071,8072,3171,4571,4572,2182,1722,8073,3238,3239, # 7958
-1842,3610,1715, 481, 365,1975,1856,8074,8075,1962,2491,4573,8076,2126,3611,3240, # 7974
- 433,1894,2063,2075,8077, 602,2741,8078,8079,8080,8081,8082,3014,1628,3400,8083, # 7990
-3172,4574,4052,2890,4575,2512,8084,2544,2772,8085,8086,8087,3310,4576,2891,8088, # 8006
-4577,8089,2851,4578,4579,1221,2967,4053,2513,8090,8091,8092,1867,1989,8093,8094, # 8022
-8095,1895,8096,8097,4580,1896,4054, 318,8098,2094,4055,4293,8099,8100, 485,8101, # 8038
- 938,3862, 553,2670, 116,8102,3863,3612,8103,3498,2671,2773,3401,3311,2807,8104, # 8054
-3613,2929,4056,1747,2930,2968,8105,8106, 207,8107,8108,2672,4581,2514,8109,3015, # 8070
- 890,3614,3864,8110,1877,3732,3402,8111,2183,2353,3403,1652,8112,8113,8114, 941, # 8086
-2294, 208,3499,4057,2019, 330,4294,3865,2892,2492,3733,4295,8115,8116,8117,8118, # 8102
+ 1, 1800, 1506, 255, 1431, 198, 9, 82, 6, 7310, 177, 202, 3615, 1256, 2808, 110, # 2742
+ 3735, 33, 3241, 261, 76, 44, 2113, 16, 2931, 2184, 1176, 659, 3868, 26, 3404, 2643, # 2758
+ 1198, 3869, 3313, 4060, 410, 2211, 302, 590, 361, 1963, 8, 204, 58, 4296, 7311, 1931, # 2774
+ 63, 7312, 7313, 317, 1614, 75, 222, 159, 4061, 2412, 1480, 7314, 3500, 3068, 224, 2809, # 2790
+ 3616, 3, 10, 3870, 1471, 29, 2774, 1135, 2852, 1939, 873, 130, 3242, 1123, 312, 7315, # 2806
+ 4297, 2051, 507, 252, 682, 7316, 142, 1914, 124, 206, 2932, 34, 3501, 3173, 64, 604, # 2822
+ 7317, 2494, 1976, 1977, 155, 1990, 645, 641, 1606, 7318, 3405, 337, 72, 406, 7319, 80, # 2838
+ 630, 238, 3174, 1509, 263, 939, 1092, 2644, 756, 1440, 1094, 3406, 449, 69, 2969, 591, # 2854
+ 179, 2095, 471, 115, 2034, 1843, 60, 50, 2970, 134, 806, 1868, 734, 2035, 3407, 180, # 2870
+ 995, 1607, 156, 537, 2893, 688, 7320, 319, 1305, 779, 2144, 514, 2374, 298, 4298, 359, # 2886
+ 2495, 90, 2707, 1338, 663, 11, 906, 1099, 2545, 20, 2436, 182, 532, 1716, 7321, 732, # 2902
+ 1376, 4062, 1311, 1420, 3175, 25, 2312, 1056, 113, 399, 382, 1949, 242, 3408, 2467, 529, # 2918
+ 3243, 475, 1447, 3617, 7322, 117, 21, 656, 810, 1297, 2295, 2329, 3502, 7323, 126, 4063, # 2934
+ 706, 456, 150, 613, 4299, 71, 1118, 2036, 4064, 145, 3069, 85, 835, 486, 2114, 1246, # 2950
+ 1426, 428, 727, 1285, 1015, 800, 106, 623, 303, 1281, 7324, 2127, 2354, 347, 3736, 221, # 2966
+ 3503, 3110, 7325, 1955, 1153, 4065, 83, 296, 1199, 3070, 192, 624, 93, 7326, 822, 1897, # 2982
+ 2810, 3111, 795, 2064, 991, 1554, 1542, 1592, 27, 43, 2853, 859, 139, 1456, 860, 4300, # 2998
+ 437, 712, 3871, 164, 2392, 3112, 695, 211, 3017, 2096, 195, 3872, 1608, 3504, 3505, 3618, # 3014
+ 3873, 234, 811, 2971, 2097, 3874, 2229, 1441, 3506, 1615, 2375, 668, 2076, 1638, 305, 228, # 3030
+ 1664, 4301, 467, 415, 7327, 262, 2098, 1593, 239, 108, 300, 200, 1033, 512, 1247, 2077, # 3046
+ 7328, 7329, 2173, 3176, 3619, 2673, 593, 845, 1062, 3244, 88, 1723, 2037, 3875, 1950, 212, # 3062
+ 266, 152, 149, 468, 1898, 4066, 4302, 77, 187, 7330, 3018, 37, 5, 2972, 7331, 3876, # 3078
+ 7332, 7333, 39, 2517, 4303, 2894, 3177, 2078, 55, 148, 74, 4304, 545, 483, 1474, 1029, # 3094
+ 1665, 217, 1869, 1531, 3113, 1104, 2645, 4067, 24, 172, 3507, 900, 3877, 3508, 3509, 4305, # 3110
+ 32, 1408, 2811, 1312, 329, 487, 2355, 2247, 2708, 784, 2674, 4, 3019, 3314, 1427, 1788, # 3126
+ 188, 109, 499, 7334, 3620, 1717, 1789, 888, 1217, 3020, 4306, 7335, 3510, 7336, 3315, 1520, # 3142
+ 3621, 3878, 196, 1034, 775, 7337, 7338, 929, 1815, 249, 439, 38, 7339, 1063, 7340, 794, # 3158
+ 3879, 1435, 2296, 46, 178, 3245, 2065, 7341, 2376, 7342, 214, 1709, 4307, 804, 35, 707, # 3174
+ 324, 3622, 1601, 2546, 140, 459, 4068, 7343, 7344, 1365, 839, 272, 978, 2257, 2572, 3409, # 3190
+ 2128, 1363, 3623, 1423, 697, 100, 3071, 48, 70, 1231, 495, 3114, 2193, 7345, 1294, 7346, # 3206
+ 2079, 462, 586, 1042, 3246, 853, 256, 988, 185, 2377, 3410, 1698, 434, 1084, 7347, 3411, # 3222
+ 314, 2615, 2775, 4308, 2330, 2331, 569, 2280, 637, 1816, 2518, 757, 1162, 1878, 1616, 3412, # 3238
+ 287, 1577, 2115, 768, 4309, 1671, 2854, 3511, 2519, 1321, 3737, 909, 2413, 7348, 4069, 933, # 3254
+ 3738, 7349, 2052, 2356, 1222, 4310, 765, 2414, 1322, 786, 4311, 7350, 1919, 1462, 1677, 2895, # 3270
+ 1699, 7351, 4312, 1424, 2437, 3115, 3624, 2590, 3316, 1774, 1940, 3413, 3880, 4070, 309, 1369, # 3286
+ 1130, 2812, 364, 2230, 1653, 1299, 3881, 3512, 3882, 3883, 2646, 525, 1085, 3021, 902, 2000, # 3302
+ 1475, 964, 4313, 421, 1844, 1415, 1057, 2281, 940, 1364, 3116, 376, 4314, 4315, 1381, 7, # 3318
+ 2520, 983, 2378, 336, 1710, 2675, 1845, 321, 3414, 559, 1131, 3022, 2742, 1808, 1132, 1313, # 3334
+ 265, 1481, 1857, 7352, 352, 1203, 2813, 3247, 167, 1089, 420, 2814, 776, 792, 1724, 3513, # 3350
+ 4071, 2438, 3248, 7353, 4072, 7354, 446, 229, 333, 2743, 901, 3739, 1200, 1557, 4316, 2647, # 3366
+ 1920, 395, 2744, 2676, 3740, 4073, 1835, 125, 916, 3178, 2616, 4317, 7355, 7356, 3741, 7357, # 3382
+ 7358, 7359, 4318, 3117, 3625, 1133, 2547, 1757, 3415, 1510, 2313, 1409, 3514, 7360, 2145, 438, # 3398
+ 2591, 2896, 2379, 3317, 1068, 958, 3023, 461, 311, 2855, 2677, 4074, 1915, 3179, 4075, 1978, # 3414
+ 383, 750, 2745, 2617, 4076, 274, 539, 385, 1278, 1442, 7361, 1154, 1964, 384, 561, 210, # 3430
+ 98, 1295, 2548, 3515, 7362, 1711, 2415, 1482, 3416, 3884, 2897, 1257, 129, 7363, 3742, 642, # 3446
+ 523, 2776, 2777, 2648, 7364, 141, 2231, 1333, 68, 176, 441, 876, 907, 4077, 603, 2592, # 3462
+ 710, 171, 3417, 404, 549, 18, 3118, 2393, 1410, 3626, 1666, 7365, 3516, 4319, 2898, 4320, # 3478
+ 7366, 2973, 368, 7367, 146, 366, 99, 871, 3627, 1543, 748, 807, 1586, 1185, 22, 2258, # 3494
+ 379, 3743, 3180, 7368, 3181, 505, 1941, 2618, 1991, 1382, 2314, 7369, 380, 2357, 218, 702, # 3510
+ 1817, 1248, 3418, 3024, 3517, 3318, 3249, 7370, 2974, 3628, 930, 3250, 3744, 7371, 59, 7372, # 3526
+ 585, 601, 4078, 497, 3419, 1112, 1314, 4321, 1801, 7373, 1223, 1472, 2174, 7374, 749, 1836, # 3542
+ 690, 1899, 3745, 1772, 3885, 1476, 429, 1043, 1790, 2232, 2116, 917, 4079, 447, 1086, 1629, # 3558
+ 7375, 556, 7376, 7377, 2020, 1654, 844, 1090, 105, 550, 966, 1758, 2815, 1008, 1782, 686, # 3574
+ 1095, 7378, 2282, 793, 1602, 7379, 3518, 2593, 4322, 4080, 2933, 2297, 4323, 3746, 980, 2496, # 3590
+ 544, 353, 527, 4324, 908, 2678, 2899, 7380, 381, 2619, 1942, 1348, 7381, 1341, 1252, 560, # 3606
+ 3072, 7382, 3420, 2856, 7383, 2053, 973, 886, 2080, 143, 4325, 7384, 7385, 157, 3886, 496, # 3622
+ 4081, 57, 840, 540, 2038, 4326, 4327, 3421, 2117, 1445, 970, 2259, 1748, 1965, 2081, 4082, # 3638
+ 3119, 1234, 1775, 3251, 2816, 3629, 773, 1206, 2129, 1066, 2039, 1326, 3887, 1738, 1725, 4083, # 3654
+ 279, 3120, 51, 1544, 2594, 423, 1578, 2130, 2066, 173, 4328, 1879, 7386, 7387, 1583, 264, # 3670
+ 610, 3630, 4329, 2439, 280, 154, 7388, 7389, 7390, 1739, 338, 1282, 3073, 693, 2857, 1411, # 3686
+ 1074, 3747, 2440, 7391, 4330, 7392, 7393, 1240, 952, 2394, 7394, 2900, 1538, 2679, 685, 1483, # 3702
+ 4084, 2468, 1436, 953, 4085, 2054, 4331, 671, 2395, 79, 4086, 2441, 3252, 608, 567, 2680, # 3718
+ 3422, 4087, 4088, 1691, 393, 1261, 1791, 2396, 7395, 4332, 7396, 7397, 7398, 7399, 1383, 1672, # 3734
+ 3748, 3182, 1464, 522, 1119, 661, 1150, 216, 675, 4333, 3888, 1432, 3519, 609, 4334, 2681, # 3750
+ 2397, 7400, 7401, 7402, 4089, 3025, 0, 7403, 2469, 315, 231, 2442, 301, 3319, 4335, 2380, # 3766
+ 7404, 233, 4090, 3631, 1818, 4336, 4337, 7405, 96, 1776, 1315, 2082, 7406, 257, 7407, 1809, # 3782
+ 3632, 2709, 1139, 1819, 4091, 2021, 1124, 2163, 2778, 1777, 2649, 7408, 3074, 363, 1655, 3183, # 3798
+ 7409, 2975, 7410, 7411, 7412, 3889, 1567, 3890, 718, 103, 3184, 849, 1443, 341, 3320, 2934, # 3814
+ 1484, 7413, 1712, 127, 67, 339, 4092, 2398, 679, 1412, 821, 7414, 7415, 834, 738, 351, # 3830
+ 2976, 2146, 846, 235, 1497, 1880, 418, 1992, 3749, 2710, 186, 1100, 2147, 2746, 3520, 1545, # 3846
+ 1355, 2935, 2858, 1377, 583, 3891, 4093, 2573, 2977, 7416, 1298, 3633, 1078, 2549, 3634, 2358, # 3862
+ 78, 3750, 3751, 267, 1289, 2099, 2001, 1594, 4094, 348, 369, 1274, 2194, 2175, 1837, 4338, # 3878
+ 1820, 2817, 3635, 2747, 2283, 2002, 4339, 2936, 2748, 144, 3321, 882, 4340, 3892, 2749, 3423, # 3894
+ 4341, 2901, 7417, 4095, 1726, 320, 7418, 3893, 3026, 788, 2978, 7419, 2818, 1773, 1327, 2859, # 3910
+ 3894, 2819, 7420, 1306, 4342, 2003, 1700, 3752, 3521, 2359, 2650, 787, 2022, 506, 824, 3636, # 3926
+ 534, 323, 4343, 1044, 3322, 2023, 1900, 946, 3424, 7421, 1778, 1500, 1678, 7422, 1881, 4344, # 3942
+ 165, 243, 4345, 3637, 2521, 123, 683, 4096, 764, 4346, 36, 3895, 1792, 589, 2902, 816, # 3958
+ 626, 1667, 3027, 2233, 1639, 1555, 1622, 3753, 3896, 7423, 3897, 2860, 1370, 1228, 1932, 891, # 3974
+ 2083, 2903, 304, 4097, 7424, 292, 2979, 2711, 3522, 691, 2100, 4098, 1115, 4347, 118, 662, # 3990
+ 7425, 611, 1156, 854, 2381, 1316, 2861, 2, 386, 515, 2904, 7426, 7427, 3253, 868, 2234, # 4006
+ 1486, 855, 2651, 785, 2212, 3028, 7428, 1040, 3185, 3523, 7429, 3121, 448, 7430, 1525, 7431, # 4022
+ 2164, 4348, 7432, 3754, 7433, 4099, 2820, 3524, 3122, 503, 818, 3898, 3123, 1568, 814, 676, # 4038
+ 1444, 306, 1749, 7434, 3755, 1416, 1030, 197, 1428, 805, 2821, 1501, 4349, 7435, 7436, 7437, # 4054
+ 1993, 7438, 4350, 7439, 7440, 2195, 13, 2779, 3638, 2980, 3124, 1229, 1916, 7441, 3756, 2131, # 4070
+ 7442, 4100, 4351, 2399, 3525, 7443, 2213, 1511, 1727, 1120, 7444, 7445, 646, 3757, 2443, 307, # 4086
+ 7446, 7447, 1595, 3186, 7448, 7449, 7450, 3639, 1113, 1356, 3899, 1465, 2522, 2523, 7451, 519, # 4102
+ 7452, 128, 2132, 92, 2284, 1979, 7453, 3900, 1512, 342, 3125, 2196, 7454, 2780, 2214, 1980, # 4118
+ 3323, 7455, 290, 1656, 1317, 789, 827, 2360, 7456, 3758, 4352, 562, 581, 3901, 7457, 401, # 4134
+ 4353, 2248, 94, 4354, 1399, 2781, 7458, 1463, 2024, 4355, 3187, 1943, 7459, 828, 1105, 4101, # 4150
+ 1262, 1394, 7460, 4102, 605, 4356, 7461, 1783, 2862, 7462, 2822, 819, 2101, 578, 2197, 2937, # 4166
+ 7463, 1502, 436, 3254, 4103, 3255, 2823, 3902, 2905, 3425, 3426, 7464, 2712, 2315, 7465, 7466, # 4182
+ 2332, 2067, 23, 4357, 193, 826, 3759, 2102, 699, 1630, 4104, 3075, 390, 1793, 1064, 3526, # 4198
+ 7467, 1579, 3076, 3077, 1400, 7468, 4105, 1838, 1640, 2863, 7469, 4358, 4359, 137, 4106, 598, # 4214
+ 3078, 1966, 780, 104, 974, 2938, 7470, 278, 899, 253, 402, 572, 504, 493, 1339, 7471, # 4230
+ 3903, 1275, 4360, 2574, 2550, 7472, 3640, 3029, 3079, 2249, 565, 1334, 2713, 863, 41, 7473, # 4246
+ 7474, 4361, 7475, 1657, 2333, 19, 463, 2750, 4107, 606, 7476, 2981, 3256, 1087, 2084, 1323, # 4262
+ 2652, 2982, 7477, 1631, 1623, 1750, 4108, 2682, 7478, 2864, 791, 2714, 2653, 2334, 232, 2416, # 4278
+ 7479, 2983, 1498, 7480, 2654, 2620, 755, 1366, 3641, 3257, 3126, 2025, 1609, 119, 1917, 3427, # 4294
+ 862, 1026, 4109, 7481, 3904, 3760, 4362, 3905, 4363, 2260, 1951, 2470, 7482, 1125, 817, 4110, # 4310
+ 4111, 3906, 1513, 1766, 2040, 1487, 4112, 3030, 3258, 2824, 3761, 3127, 7483, 7484, 1507, 7485, # 4326
+ 2683, 733, 40, 1632, 1106, 2865, 345, 4113, 841, 2524, 230, 4364, 2984, 1846, 3259, 3428, # 4342
+ 7486, 1263, 986, 3429, 7487, 735, 879, 254, 1137, 857, 622, 1300, 1180, 1388, 1562, 3907, # 4358
+ 3908, 2939, 967, 2751, 2655, 1349, 592, 2133, 1692, 3324, 2985, 1994, 4114, 1679, 3909, 1901, # 4374
+ 2185, 7488, 739, 3642, 2715, 1296, 1290, 7489, 4115, 2198, 2199, 1921, 1563, 2595, 2551, 1870, # 4390
+ 2752, 2986, 7490, 435, 7491, 343, 1108, 596, 17, 1751, 4365, 2235, 3430, 3643, 7492, 4366, # 4406
+ 294, 3527, 2940, 1693, 477, 979, 281, 2041, 3528, 643, 2042, 3644, 2621, 2782, 2261, 1031, # 4422
+ 2335, 2134, 2298, 3529, 4367, 367, 1249, 2552, 7493, 3530, 7494, 4368, 1283, 3325, 2004, 240, # 4438
+ 1762, 3326, 4369, 4370, 836, 1069, 3128, 474, 7495, 2148, 2525, 268, 3531, 7496, 3188, 1521, # 4454
+ 1284, 7497, 1658, 1546, 4116, 7498, 3532, 3533, 7499, 4117, 3327, 2684, 1685, 4118, 961, 1673, # 4470
+ 2622, 190, 2005, 2200, 3762, 4371, 4372, 7500, 570, 2497, 3645, 1490, 7501, 4373, 2623, 3260, # 4486
+ 1956, 4374, 584, 1514, 396, 1045, 1944, 7502, 4375, 1967, 2444, 7503, 7504, 4376, 3910, 619, # 4502
+ 7505, 3129, 3261, 215, 2006, 2783, 2553, 3189, 4377, 3190, 4378, 763, 4119, 3763, 4379, 7506, # 4518
+ 7507, 1957, 1767, 2941, 3328, 3646, 1174, 452, 1477, 4380, 3329, 3130, 7508, 2825, 1253, 2382, # 4534
+ 2186, 1091, 2285, 4120, 492, 7509, 638, 1169, 1824, 2135, 1752, 3911, 648, 926, 1021, 1324, # 4550
+ 4381, 520, 4382, 997, 847, 1007, 892, 4383, 3764, 2262, 1871, 3647, 7510, 2400, 1784, 4384, # 4566
+ 1952, 2942, 3080, 3191, 1728, 4121, 2043, 3648, 4385, 2007, 1701, 3131, 1551, 30, 2263, 4122, # 4582
+ 7511, 2026, 4386, 3534, 7512, 501, 7513, 4123, 594, 3431, 2165, 1821, 3535, 3432, 3536, 3192, # 4598
+ 829, 2826, 4124, 7514, 1680, 3132, 1225, 4125, 7515, 3262, 4387, 4126, 3133, 2336, 7516, 4388, # 4614
+ 4127, 7517, 3912, 3913, 7518, 1847, 2383, 2596, 3330, 7519, 4389, 374, 3914, 652, 4128, 4129, # 4630
+ 375, 1140, 798, 7520, 7521, 7522, 2361, 4390, 2264, 546, 1659, 138, 3031, 2445, 4391, 7523, # 4646
+ 2250, 612, 1848, 910, 796, 3765, 1740, 1371, 825, 3766, 3767, 7524, 2906, 2554, 7525, 692, # 4662
+ 444, 3032, 2624, 801, 4392, 4130, 7526, 1491, 244, 1053, 3033, 4131, 4132, 340, 7527, 3915, # 4678
+ 1041, 2987, 293, 1168, 87, 1357, 7528, 1539, 959, 7529, 2236, 721, 694, 4133, 3768, 219, # 4694
+ 1478, 644, 1417, 3331, 2656, 1413, 1401, 1335, 1389, 3916, 7530, 7531, 2988, 2362, 3134, 1825, # 4710
+ 730, 1515, 184, 2827, 66, 4393, 7532, 1660, 2943, 246, 3332, 378, 1457, 226, 3433, 975, # 4726
+ 3917, 2944, 1264, 3537, 674, 696, 7533, 163, 7534, 1141, 2417, 2166, 713, 3538, 3333, 4394, # 4742
+ 3918, 7535, 7536, 1186, 15, 7537, 1079, 1070, 7538, 1522, 3193, 3539, 276, 1050, 2716, 758, # 4758
+ 1126, 653, 2945, 3263, 7539, 2337, 889, 3540, 3919, 3081, 2989, 903, 1250, 4395, 3920, 3434, # 4774
+ 3541, 1342, 1681, 1718, 766, 3264, 286, 89, 2946, 3649, 7540, 1713, 7541, 2597, 3334, 2990, # 4790
+ 7542, 2947, 2215, 3194, 2866, 7543, 4396, 2498, 2526, 181, 387, 1075, 3921, 731, 2187, 3335, # 4806
+ 7544, 3265, 310, 313, 3435, 2299, 770, 4134, 54, 3034, 189, 4397, 3082, 3769, 3922, 7545, # 4822
+ 1230, 1617, 1849, 355, 3542, 4135, 4398, 3336, 111, 4136, 3650, 1350, 3135, 3436, 3035, 4137, # 4838
+ 2149, 3266, 3543, 7546, 2784, 3923, 3924, 2991, 722, 2008, 7547, 1071, 247, 1207, 2338, 2471, # 4854
+ 1378, 4399, 2009, 864, 1437, 1214, 4400, 373, 3770, 1142, 2216, 667, 4401, 442, 2753, 2555, # 4870
+ 3771, 3925, 1968, 4138, 3267, 1839, 837, 170, 1107, 934, 1336, 1882, 7548, 7549, 2118, 4139, # 4886
+ 2828, 743, 1569, 7550, 4402, 4140, 582, 2384, 1418, 3437, 7551, 1802, 7552, 357, 1395, 1729, # 4902
+ 3651, 3268, 2418, 1564, 2237, 7553, 3083, 3772, 1633, 4403, 1114, 2085, 4141, 1532, 7554, 482, # 4918
+ 2446, 4404, 7555, 7556, 1492, 833, 1466, 7557, 2717, 3544, 1641, 2829, 7558, 1526, 1272, 3652, # 4934
+ 4142, 1686, 1794, 416, 2556, 1902, 1953, 1803, 7559, 3773, 2785, 3774, 1159, 2316, 7560, 2867, # 4950
+ 4405, 1610, 1584, 3036, 2419, 2754, 443, 3269, 1163, 3136, 7561, 7562, 3926, 7563, 4143, 2499, # 4966
+ 3037, 4406, 3927, 3137, 2103, 1647, 3545, 2010, 1872, 4144, 7564, 4145, 431, 3438, 7565, 250, # 4982
+ 97, 81, 4146, 7566, 1648, 1850, 1558, 160, 848, 7567, 866, 740, 1694, 7568, 2201, 2830, # 4998
+ 3195, 4147, 4407, 3653, 1687, 950, 2472, 426, 469, 3196, 3654, 3655, 3928, 7569, 7570, 1188, # 5014
+ 424, 1995, 861, 3546, 4148, 3775, 2202, 2685, 168, 1235, 3547, 4149, 7571, 2086, 1674, 4408, # 5030
+ 3337, 3270, 220, 2557, 1009, 7572, 3776, 670, 2992, 332, 1208, 717, 7573, 7574, 3548, 2447, # 5046
+ 3929, 3338, 7575, 513, 7576, 1209, 2868, 3339, 3138, 4409, 1080, 7577, 7578, 7579, 7580, 2527, # 5062
+ 3656, 3549, 815, 1587, 3930, 3931, 7581, 3550, 3439, 3777, 1254, 4410, 1328, 3038, 1390, 3932, # 5078
+ 1741, 3933, 3778, 3934, 7582, 236, 3779, 2448, 3271, 7583, 7584, 3657, 3780, 1273, 3781, 4411, # 5094
+ 7585, 308, 7586, 4412, 245, 4413, 1851, 2473, 1307, 2575, 430, 715, 2136, 2449, 7587, 270, # 5110
+ 199, 2869, 3935, 7588, 3551, 2718, 1753, 761, 1754, 725, 1661, 1840, 4414, 3440, 3658, 7589, # 5126
+ 7590, 587, 14, 3272, 227, 2598, 326, 480, 2265, 943, 2755, 3552, 291, 650, 1883, 7591, # 5142
+ 1702, 1226, 102, 1547, 62, 3441, 904, 4415, 3442, 1164, 4150, 7592, 7593, 1224, 1548, 2756, # 5158
+ 391, 498, 1493, 7594, 1386, 1419, 7595, 2055, 1177, 4416, 813, 880, 1081, 2363, 566, 1145, # 5174
+ 4417, 2286, 1001, 1035, 2558, 2599, 2238, 394, 1286, 7596, 7597, 2068, 7598, 86, 1494, 1730, # 5190
+ 3936, 491, 1588, 745, 897, 2948, 843, 3340, 3937, 2757, 2870, 3273, 1768, 998, 2217, 2069, # 5206
+ 397, 1826, 1195, 1969, 3659, 2993, 3341, 284, 7599, 3782, 2500, 2137, 2119, 1903, 7600, 3938, # 5222
+ 2150, 3939, 4151, 1036, 3443, 1904, 114, 2559, 4152, 209, 1527, 7601, 7602, 2949, 2831, 2625, # 5238
+ 2385, 2719, 3139, 812, 2560, 7603, 3274, 7604, 1559, 737, 1884, 3660, 1210, 885, 28, 2686, # 5254
+ 3553, 3783, 7605, 4153, 1004, 1779, 4418, 7606, 346, 1981, 2218, 2687, 4419, 3784, 1742, 797, # 5270
+ 1642, 3940, 1933, 1072, 1384, 2151, 896, 3941, 3275, 3661, 3197, 2871, 3554, 7607, 2561, 1958, # 5286
+ 4420, 2450, 1785, 7608, 7609, 7610, 3942, 4154, 1005, 1308, 3662, 4155, 2720, 4421, 4422, 1528, # 5302
+ 2600, 161, 1178, 4156, 1982, 987, 4423, 1101, 4157, 631, 3943, 1157, 3198, 2420, 1343, 1241, # 5318
+ 1016, 2239, 2562, 372, 877, 2339, 2501, 1160, 555, 1934, 911, 3944, 7611, 466, 1170, 169, # 5334
+ 1051, 2907, 2688, 3663, 2474, 2994, 1182, 2011, 2563, 1251, 2626, 7612, 992, 2340, 3444, 1540, # 5350
+ 2721, 1201, 2070, 2401, 1996, 2475, 7613, 4424, 528, 1922, 2188, 1503, 1873, 1570, 2364, 3342, # 5366
+ 3276, 7614, 557, 1073, 7615, 1827, 3445, 2087, 2266, 3140, 3039, 3084, 767, 3085, 2786, 4425, # 5382
+ 1006, 4158, 4426, 2341, 1267, 2176, 3664, 3199, 778, 3945, 3200, 2722, 1597, 2657, 7616, 4427, # 5398
+ 7617, 3446, 7618, 7619, 7620, 3277, 2689, 1433, 3278, 131, 95, 1504, 3946, 723, 4159, 3141, # 5414
+ 1841, 3555, 2758, 2189, 3947, 2027, 2104, 3665, 7621, 2995, 3948, 1218, 7622, 3343, 3201, 3949, # 5430
+ 4160, 2576, 248, 1634, 3785, 912, 7623, 2832, 3666, 3040, 3786, 654, 53, 7624, 2996, 7625, # 5446
+ 1688, 4428, 777, 3447, 1032, 3950, 1425, 7626, 191, 820, 2120, 2833, 971, 4429, 931, 3202, # 5462
+ 135, 664, 783, 3787, 1997, 772, 2908, 1935, 3951, 3788, 4430, 2909, 3203, 282, 2723, 640, # 5478
+ 1372, 3448, 1127, 922, 325, 3344, 7627, 7628, 711, 2044, 7629, 7630, 3952, 2219, 2787, 1936, # 5494
+ 3953, 3345, 2220, 2251, 3789, 2300, 7631, 4431, 3790, 1258, 3279, 3954, 3204, 2138, 2950, 3955, # 5510
+ 3956, 7632, 2221, 258, 3205, 4432, 101, 1227, 7633, 3280, 1755, 7634, 1391, 3281, 7635, 2910, # 5526
+ 2056, 893, 7636, 7637, 7638, 1402, 4161, 2342, 7639, 7640, 3206, 3556, 7641, 7642, 878, 1325, # 5542
+ 1780, 2788, 4433, 259, 1385, 2577, 744, 1183, 2267, 4434, 7643, 3957, 2502, 7644, 684, 1024, # 5558
+ 4162, 7645, 472, 3557, 3449, 1165, 3282, 3958, 3959, 322, 2152, 881, 455, 1695, 1152, 1340, # 5574
+ 660, 554, 2153, 4435, 1058, 4436, 4163, 830, 1065, 3346, 3960, 4437, 1923, 7646, 1703, 1918, # 5590
+ 7647, 932, 2268, 122, 7648, 4438, 947, 677, 7649, 3791, 2627, 297, 1905, 1924, 2269, 4439, # 5606
+ 2317, 3283, 7650, 7651, 4164, 7652, 4165, 84, 4166, 112, 989, 7653, 547, 1059, 3961, 701, # 5622
+ 3558, 1019, 7654, 4167, 7655, 3450, 942, 639, 457, 2301, 2451, 993, 2951, 407, 851, 494, # 5638
+ 4440, 3347, 927, 7656, 1237, 7657, 2421, 3348, 573, 4168, 680, 921, 2911, 1279, 1874, 285, # 5654
+ 790, 1448, 1983, 719, 2167, 7658, 7659, 4441, 3962, 3963, 1649, 7660, 1541, 563, 7661, 1077, # 5670
+ 7662, 3349, 3041, 3451, 511, 2997, 3964, 3965, 3667, 3966, 1268, 2564, 3350, 3207, 4442, 4443, # 5686
+ 7663, 535, 1048, 1276, 1189, 2912, 2028, 3142, 1438, 1373, 2834, 2952, 1134, 2012, 7664, 4169, # 5702
+ 1238, 2578, 3086, 1259, 7665, 700, 7666, 2953, 3143, 3668, 4170, 7667, 4171, 1146, 1875, 1906, # 5718
+ 4444, 2601, 3967, 781, 2422, 132, 1589, 203, 147, 273, 2789, 2402, 898, 1786, 2154, 3968, # 5734
+ 3969, 7668, 3792, 2790, 7669, 7670, 4445, 4446, 7671, 3208, 7672, 1635, 3793, 965, 7673, 1804, # 5750
+ 2690, 1516, 3559, 1121, 1082, 1329, 3284, 3970, 1449, 3794, 65, 1128, 2835, 2913, 2759, 1590, # 5766
+ 3795, 7674, 7675, 12, 2658, 45, 976, 2579, 3144, 4447, 517, 2528, 1013, 1037, 3209, 7676, # 5782
+ 3796, 2836, 7677, 3797, 7678, 3452, 7679, 2602, 614, 1998, 2318, 3798, 3087, 2724, 2628, 7680, # 5798
+ 2580, 4172, 599, 1269, 7681, 1810, 3669, 7682, 2691, 3088, 759, 1060, 489, 1805, 3351, 3285, # 5814
+ 1358, 7683, 7684, 2386, 1387, 1215, 2629, 2252, 490, 7685, 7686, 4173, 1759, 2387, 2343, 7687, # 5830
+ 4448, 3799, 1907, 3971, 2630, 1806, 3210, 4449, 3453, 3286, 2760, 2344, 874, 7688, 7689, 3454, # 5846
+ 3670, 1858, 91, 2914, 3671, 3042, 3800, 4450, 7690, 3145, 3972, 2659, 7691, 3455, 1202, 1403, # 5862
+ 3801, 2954, 2529, 1517, 2503, 4451, 3456, 2504, 7692, 4452, 7693, 2692, 1885, 1495, 1731, 3973, # 5878
+ 2365, 4453, 7694, 2029, 7695, 7696, 3974, 2693, 1216, 237, 2581, 4174, 2319, 3975, 3802, 4454, # 5894
+ 4455, 2694, 3560, 3457, 445, 4456, 7697, 7698, 7699, 7700, 2761, 61, 3976, 3672, 1822, 3977, # 5910
+ 7701, 687, 2045, 935, 925, 405, 2660, 703, 1096, 1859, 2725, 4457, 3978, 1876, 1367, 2695, # 5926
+ 3352, 918, 2105, 1781, 2476, 334, 3287, 1611, 1093, 4458, 564, 3146, 3458, 3673, 3353, 945, # 5942
+ 2631, 2057, 4459, 7702, 1925, 872, 4175, 7703, 3459, 2696, 3089, 349, 4176, 3674, 3979, 4460, # 5958
+ 3803, 4177, 3675, 2155, 3980, 4461, 4462, 4178, 4463, 2403, 2046, 782, 3981, 400, 251, 4179, # 5974
+ 1624, 7704, 7705, 277, 3676, 299, 1265, 476, 1191, 3804, 2121, 4180, 4181, 1109, 205, 7706, # 5990
+ 2582, 1000, 2156, 3561, 1860, 7707, 7708, 7709, 4464, 7710, 4465, 2565, 107, 2477, 2157, 3982, # 6006
+ 3460, 3147, 7711, 1533, 541, 1301, 158, 753, 4182, 2872, 3562, 7712, 1696, 370, 1088, 4183, # 6022
+ 4466, 3563, 579, 327, 440, 162, 2240, 269, 1937, 1374, 3461, 968, 3043, 56, 1396, 3090, # 6038
+ 2106, 3288, 3354, 7713, 1926, 2158, 4467, 2998, 7714, 3564, 7715, 7716, 3677, 4468, 2478, 7717, # 6054
+ 2791, 7718, 1650, 4469, 7719, 2603, 7720, 7721, 3983, 2661, 3355, 1149, 3356, 3984, 3805, 3985, # 6070
+ 7722, 1076, 49, 7723, 951, 3211, 3289, 3290, 450, 2837, 920, 7724, 1811, 2792, 2366, 4184, # 6086
+ 1908, 1138, 2367, 3806, 3462, 7725, 3212, 4470, 1909, 1147, 1518, 2423, 4471, 3807, 7726, 4472, # 6102
+ 2388, 2604, 260, 1795, 3213, 7727, 7728, 3808, 3291, 708, 7729, 3565, 1704, 7730, 3566, 1351, # 6118
+ 1618, 3357, 2999, 1886, 944, 4185, 3358, 4186, 3044, 3359, 4187, 7731, 3678, 422, 413, 1714, # 6134
+ 3292, 500, 2058, 2345, 4188, 2479, 7732, 1344, 1910, 954, 7733, 1668, 7734, 7735, 3986, 2404, # 6150
+ 4189, 3567, 3809, 4190, 7736, 2302, 1318, 2505, 3091, 133, 3092, 2873, 4473, 629, 31, 2838, # 6166
+ 2697, 3810, 4474, 850, 949, 4475, 3987, 2955, 1732, 2088, 4191, 1496, 1852, 7737, 3988, 620, # 6182
+ 3214, 981, 1242, 3679, 3360, 1619, 3680, 1643, 3293, 2139, 2452, 1970, 1719, 3463, 2168, 7738, # 6198
+ 3215, 7739, 7740, 3361, 1828, 7741, 1277, 4476, 1565, 2047, 7742, 1636, 3568, 3093, 7743, 869, # 6214
+ 2839, 655, 3811, 3812, 3094, 3989, 3000, 3813, 1310, 3569, 4477, 7744, 7745, 7746, 1733, 558, # 6230
+ 4478, 3681, 335, 1549, 3045, 1756, 4192, 3682, 1945, 3464, 1829, 1291, 1192, 470, 2726, 2107, # 6246
+ 2793, 913, 1054, 3990, 7747, 1027, 7748, 3046, 3991, 4479, 982, 2662, 3362, 3148, 3465, 3216, # 6262
+ 3217, 1946, 2794, 7749, 571, 4480, 7750, 1830, 7751, 3570, 2583, 1523, 2424, 7752, 2089, 984, # 6278
+ 4481, 3683, 1959, 7753, 3684, 852, 923, 2795, 3466, 3685, 969, 1519, 999, 2048, 2320, 1705, # 6294
+ 7754, 3095, 615, 1662, 151, 597, 3992, 2405, 2321, 1049, 275, 4482, 3686, 4193, 568, 3687, # 6310
+ 3571, 2480, 4194, 3688, 7755, 2425, 2270, 409, 3218, 7756, 1566, 2874, 3467, 1002, 769, 2840, # 6326
+ 194, 2090, 3149, 3689, 2222, 3294, 4195, 628, 1505, 7757, 7758, 1763, 2177, 3001, 3993, 521, # 6342
+ 1161, 2584, 1787, 2203, 2406, 4483, 3994, 1625, 4196, 4197, 412, 42, 3096, 464, 7759, 2632, # 6358
+ 4484, 3363, 1760, 1571, 2875, 3468, 2530, 1219, 2204, 3814, 2633, 2140, 2368, 4485, 4486, 3295, # 6374
+ 1651, 3364, 3572, 7760, 7761, 3573, 2481, 3469, 7762, 3690, 7763, 7764, 2271, 2091, 460, 7765, # 6390
+ 4487, 7766, 3002, 962, 588, 3574, 289, 3219, 2634, 1116, 52, 7767, 3047, 1796, 7768, 7769, # 6406
+ 7770, 1467, 7771, 1598, 1143, 3691, 4198, 1984, 1734, 1067, 4488, 1280, 3365, 465, 4489, 1572, # 6422
+ 510, 7772, 1927, 2241, 1812, 1644, 3575, 7773, 4490, 3692, 7774, 7775, 2663, 1573, 1534, 7776, # 6438
+ 7777, 4199, 536, 1807, 1761, 3470, 3815, 3150, 2635, 7778, 7779, 7780, 4491, 3471, 2915, 1911, # 6454
+ 2796, 7781, 3296, 1122, 377, 3220, 7782, 360, 7783, 7784, 4200, 1529, 551, 7785, 2059, 3693, # 6470
+ 1769, 2426, 7786, 2916, 4201, 3297, 3097, 2322, 2108, 2030, 4492, 1404, 136, 1468, 1479, 672, # 6486
+ 1171, 3221, 2303, 271, 3151, 7787, 2762, 7788, 2049, 678, 2727, 865, 1947, 4493, 7789, 2013, # 6502
+ 3995, 2956, 7790, 2728, 2223, 1397, 3048, 3694, 4494, 4495, 1735, 2917, 3366, 3576, 7791, 3816, # 6518
+ 509, 2841, 2453, 2876, 3817, 7792, 7793, 3152, 3153, 4496, 4202, 2531, 4497, 2304, 1166, 1010, # 6534
+ 552, 681, 1887, 7794, 7795, 2957, 2958, 3996, 1287, 1596, 1861, 3154, 358, 453, 736, 175, # 6550
+ 478, 1117, 905, 1167, 1097, 7796, 1853, 1530, 7797, 1706, 7798, 2178, 3472, 2287, 3695, 3473, # 6566
+ 3577, 4203, 2092, 4204, 7799, 3367, 1193, 2482, 4205, 1458, 2190, 2205, 1862, 1888, 1421, 3298, # 6582
+ 2918, 3049, 2179, 3474, 595, 2122, 7800, 3997, 7801, 7802, 4206, 1707, 2636, 223, 3696, 1359, # 6598
+ 751, 3098, 183, 3475, 7803, 2797, 3003, 419, 2369, 633, 704, 3818, 2389, 241, 7804, 7805, # 6614
+ 7806, 838, 3004, 3697, 2272, 2763, 2454, 3819, 1938, 2050, 3998, 1309, 3099, 2242, 1181, 7807, # 6630
+ 1136, 2206, 3820, 2370, 1446, 4207, 2305, 4498, 7808, 7809, 4208, 1055, 2605, 484, 3698, 7810, # 6646
+ 3999, 625, 4209, 2273, 3368, 1499, 4210, 4000, 7811, 4001, 4211, 3222, 2274, 2275, 3476, 7812, # 6662
+ 7813, 2764, 808, 2606, 3699, 3369, 4002, 4212, 3100, 2532, 526, 3370, 3821, 4213, 955, 7814, # 6678
+ 1620, 4214, 2637, 2427, 7815, 1429, 3700, 1669, 1831, 994, 928, 7816, 3578, 1260, 7817, 7818, # 6694
+ 7819, 1948, 2288, 741, 2919, 1626, 4215, 2729, 2455, 867, 1184, 362, 3371, 1392, 7820, 7821, # 6710
+ 4003, 4216, 1770, 1736, 3223, 2920, 4499, 4500, 1928, 2698, 1459, 1158, 7822, 3050, 3372, 2877, # 6726
+ 1292, 1929, 2506, 2842, 3701, 1985, 1187, 2071, 2014, 2607, 4217, 7823, 2566, 2507, 2169, 3702, # 6742
+ 2483, 3299, 7824, 3703, 4501, 7825, 7826, 666, 1003, 3005, 1022, 3579, 4218, 7827, 4502, 1813, # 6758
+ 2253, 574, 3822, 1603, 295, 1535, 705, 3823, 4219, 283, 858, 417, 7828, 7829, 3224, 4503, # 6774
+ 4504, 3051, 1220, 1889, 1046, 2276, 2456, 4004, 1393, 1599, 689, 2567, 388, 4220, 7830, 2484, # 6790
+ 802, 7831, 2798, 3824, 2060, 1405, 2254, 7832, 4505, 3825, 2109, 1052, 1345, 3225, 1585, 7833, # 6806
+ 809, 7834, 7835, 7836, 575, 2730, 3477, 956, 1552, 1469, 1144, 2323, 7837, 2324, 1560, 2457, # 6822
+ 3580, 3226, 4005, 616, 2207, 3155, 2180, 2289, 7838, 1832, 7839, 3478, 4506, 7840, 1319, 3704, # 6838
+ 3705, 1211, 3581, 1023, 3227, 1293, 2799, 7841, 7842, 7843, 3826, 607, 2306, 3827, 762, 2878, # 6854
+ 1439, 4221, 1360, 7844, 1485, 3052, 7845, 4507, 1038, 4222, 1450, 2061, 2638, 4223, 1379, 4508, # 6870
+ 2585, 7846, 7847, 4224, 1352, 1414, 2325, 2921, 1172, 7848, 7849, 3828, 3829, 7850, 1797, 1451, # 6886
+ 7851, 7852, 7853, 7854, 2922, 4006, 4007, 2485, 2346, 411, 4008, 4009, 3582, 3300, 3101, 4509, # 6902
+ 1561, 2664, 1452, 4010, 1375, 7855, 7856, 47, 2959, 316, 7857, 1406, 1591, 2923, 3156, 7858, # 6918
+ 1025, 2141, 3102, 3157, 354, 2731, 884, 2224, 4225, 2407, 508, 3706, 726, 3583, 996, 2428, # 6934
+ 3584, 729, 7859, 392, 2191, 1453, 4011, 4510, 3707, 7860, 7861, 2458, 3585, 2608, 1675, 2800, # 6950
+ 919, 2347, 2960, 2348, 1270, 4511, 4012, 73, 7862, 7863, 647, 7864, 3228, 2843, 2255, 1550, # 6966
+ 1346, 3006, 7865, 1332, 883, 3479, 7866, 7867, 7868, 7869, 3301, 2765, 7870, 1212, 831, 1347, # 6982
+ 4226, 4512, 2326, 3830, 1863, 3053, 720, 3831, 4513, 4514, 3832, 7871, 4227, 7872, 7873, 4515, # 6998
+ 7874, 7875, 1798, 4516, 3708, 2609, 4517, 3586, 1645, 2371, 7876, 7877, 2924, 669, 2208, 2665, # 7014
+ 2429, 7878, 2879, 7879, 7880, 1028, 3229, 7881, 4228, 2408, 7882, 2256, 1353, 7883, 7884, 4518, # 7030
+ 3158, 518, 7885, 4013, 7886, 4229, 1960, 7887, 2142, 4230, 7888, 7889, 3007, 2349, 2350, 3833, # 7046
+ 516, 1833, 1454, 4014, 2699, 4231, 4519, 2225, 2610, 1971, 1129, 3587, 7890, 2766, 7891, 2961, # 7062
+ 1422, 577, 1470, 3008, 1524, 3373, 7892, 7893, 432, 4232, 3054, 3480, 7894, 2586, 1455, 2508, # 7078
+ 2226, 1972, 1175, 7895, 1020, 2732, 4015, 3481, 4520, 7896, 2733, 7897, 1743, 1361, 3055, 3482, # 7094
+ 2639, 4016, 4233, 4521, 2290, 895, 924, 4234, 2170, 331, 2243, 3056, 166, 1627, 3057, 1098, # 7110
+ 7898, 1232, 2880, 2227, 3374, 4522, 657, 403, 1196, 2372, 542, 3709, 3375, 1600, 4235, 3483, # 7126
+ 7899, 4523, 2767, 3230, 576, 530, 1362, 7900, 4524, 2533, 2666, 3710, 4017, 7901, 842, 3834, # 7142
+ 7902, 2801, 2031, 1014, 4018, 213, 2700, 3376, 665, 621, 4236, 7903, 3711, 2925, 2430, 7904, # 7158
+ 2431, 3302, 3588, 3377, 7905, 4237, 2534, 4238, 4525, 3589, 1682, 4239, 3484, 1380, 7906, 724, # 7174
+ 2277, 600, 1670, 7907, 1337, 1233, 4526, 3103, 2244, 7908, 1621, 4527, 7909, 651, 4240, 7910, # 7190
+ 1612, 4241, 2611, 7911, 2844, 7912, 2734, 2307, 3058, 7913, 716, 2459, 3059, 174, 1255, 2701, # 7206
+ 4019, 3590, 548, 1320, 1398, 728, 4020, 1574, 7914, 1890, 1197, 3060, 4021, 7915, 3061, 3062, # 7222
+ 3712, 3591, 3713, 747, 7916, 635, 4242, 4528, 7917, 7918, 7919, 4243, 7920, 7921, 4529, 7922, # 7238
+ 3378, 4530, 2432, 451, 7923, 3714, 2535, 2072, 4244, 2735, 4245, 4022, 7924, 1764, 4531, 7925, # 7254
+ 4246, 350, 7926, 2278, 2390, 2486, 7927, 4247, 4023, 2245, 1434, 4024, 488, 4532, 458, 4248, # 7270
+ 4025, 3715, 771, 1330, 2391, 3835, 2568, 3159, 2159, 2409, 1553, 2667, 3160, 4249, 7928, 2487, # 7286
+ 2881, 2612, 1720, 2702, 4250, 3379, 4533, 7929, 2536, 4251, 7930, 3231, 4252, 2768, 7931, 2015, # 7302
+ 2736, 7932, 1155, 1017, 3716, 3836, 7933, 3303, 2308, 201, 1864, 4253, 1430, 7934, 4026, 7935, # 7318
+ 7936, 7937, 7938, 7939, 4254, 1604, 7940, 414, 1865, 371, 2587, 4534, 4535, 3485, 2016, 3104, # 7334
+ 4536, 1708, 960, 4255, 887, 389, 2171, 1536, 1663, 1721, 7941, 2228, 4027, 2351, 2926, 1580, # 7350
+ 7942, 7943, 7944, 1744, 7945, 2537, 4537, 4538, 7946, 4539, 7947, 2073, 7948, 7949, 3592, 3380, # 7366
+ 2882, 4256, 7950, 4257, 2640, 3381, 2802, 673, 2703, 2460, 709, 3486, 4028, 3593, 4258, 7951, # 7382
+ 1148, 502, 634, 7952, 7953, 1204, 4540, 3594, 1575, 4541, 2613, 3717, 7954, 3718, 3105, 948, # 7398
+ 3232, 121, 1745, 3837, 1110, 7955, 4259, 3063, 2509, 3009, 4029, 3719, 1151, 1771, 3838, 1488, # 7414
+ 4030, 1986, 7956, 2433, 3487, 7957, 7958, 2093, 7959, 4260, 3839, 1213, 1407, 2803, 531, 2737, # 7430
+ 2538, 3233, 1011, 1537, 7960, 2769, 4261, 3106, 1061, 7961, 3720, 3721, 1866, 2883, 7962, 2017, # 7446
+ 120, 4262, 4263, 2062, 3595, 3234, 2309, 3840, 2668, 3382, 1954, 4542, 7963, 7964, 3488, 1047, # 7462
+ 2704, 1266, 7965, 1368, 4543, 2845, 649, 3383, 3841, 2539, 2738, 1102, 2846, 2669, 7966, 7967, # 7478
+ 1999, 7968, 1111, 3596, 2962, 7969, 2488, 3842, 3597, 2804, 1854, 3384, 3722, 7970, 7971, 3385, # 7494
+ 2410, 2884, 3304, 3235, 3598, 7972, 2569, 7973, 3599, 2805, 4031, 1460, 856, 7974, 3600, 7975, # 7510
+ 2885, 2963, 7976, 2886, 3843, 7977, 4264, 632, 2510, 875, 3844, 1697, 3845, 2291, 7978, 7979, # 7526
+ 4544, 3010, 1239, 580, 4545, 4265, 7980, 914, 936, 2074, 1190, 4032, 1039, 2123, 7981, 7982, # 7542
+ 7983, 3386, 1473, 7984, 1354, 4266, 3846, 7985, 2172, 3064, 4033, 915, 3305, 4267, 4268, 3306, # 7558
+ 1605, 1834, 7986, 2739, 398, 3601, 4269, 3847, 4034, 328, 1912, 2847, 4035, 3848, 1331, 4270, # 7574
+ 3011, 937, 4271, 7987, 3602, 4036, 4037, 3387, 2160, 4546, 3388, 524, 742, 538, 3065, 1012, # 7590
+ 7988, 7989, 3849, 2461, 7990, 658, 1103, 225, 3850, 7991, 7992, 4547, 7993, 4548, 7994, 3236, # 7606
+ 1243, 7995, 4038, 963, 2246, 4549, 7996, 2705, 3603, 3161, 7997, 7998, 2588, 2327, 7999, 4550, # 7622
+ 8000, 8001, 8002, 3489, 3307, 957, 3389, 2540, 2032, 1930, 2927, 2462, 870, 2018, 3604, 1746, # 7638
+ 2770, 2771, 2434, 2463, 8003, 3851, 8004, 3723, 3107, 3724, 3490, 3390, 3725, 8005, 1179, 3066, # 7654
+ 8006, 3162, 2373, 4272, 3726, 2541, 3163, 3108, 2740, 4039, 8007, 3391, 1556, 2542, 2292, 977, # 7670
+ 2887, 2033, 4040, 1205, 3392, 8008, 1765, 3393, 3164, 2124, 1271, 1689, 714, 4551, 3491, 8009, # 7686
+ 2328, 3852, 533, 4273, 3605, 2181, 617, 8010, 2464, 3308, 3492, 2310, 8011, 8012, 3165, 8013, # 7702
+ 8014, 3853, 1987, 618, 427, 2641, 3493, 3394, 8015, 8016, 1244, 1690, 8017, 2806, 4274, 4552, # 7718
+ 8018, 3494, 8019, 8020, 2279, 1576, 473, 3606, 4275, 3395, 972, 8021, 3607, 8022, 3067, 8023, # 7734
+ 8024, 4553, 4554, 8025, 3727, 4041, 4042, 8026, 153, 4555, 356, 8027, 1891, 2888, 4276, 2143, # 7750
+ 408, 803, 2352, 8028, 3854, 8029, 4277, 1646, 2570, 2511, 4556, 4557, 3855, 8030, 3856, 4278, # 7766
+ 8031, 2411, 3396, 752, 8032, 8033, 1961, 2964, 8034, 746, 3012, 2465, 8035, 4279, 3728, 698, # 7782
+ 4558, 1892, 4280, 3608, 2543, 4559, 3609, 3857, 8036, 3166, 3397, 8037, 1823, 1302, 4043, 2706, # 7798
+ 3858, 1973, 4281, 8038, 4282, 3167, 823, 1303, 1288, 1236, 2848, 3495, 4044, 3398, 774, 3859, # 7814
+ 8039, 1581, 4560, 1304, 2849, 3860, 4561, 8040, 2435, 2161, 1083, 3237, 4283, 4045, 4284, 344, # 7830
+ 1173, 288, 2311, 454, 1683, 8041, 8042, 1461, 4562, 4046, 2589, 8043, 8044, 4563, 985, 894, # 7846
+ 8045, 3399, 3168, 8046, 1913, 2928, 3729, 1988, 8047, 2110, 1974, 8048, 4047, 8049, 2571, 1194, # 7862
+ 425, 8050, 4564, 3169, 1245, 3730, 4285, 8051, 8052, 2850, 8053, 636, 4565, 1855, 3861, 760, # 7878
+ 1799, 8054, 4286, 2209, 1508, 4566, 4048, 1893, 1684, 2293, 8055, 8056, 8057, 4287, 4288, 2210, # 7894
+ 479, 8058, 8059, 832, 8060, 4049, 2489, 8061, 2965, 2490, 3731, 990, 3109, 627, 1814, 2642, # 7910
+ 4289, 1582, 4290, 2125, 2111, 3496, 4567, 8062, 799, 4291, 3170, 8063, 4568, 2112, 1737, 3013, # 7926
+ 1018, 543, 754, 4292, 3309, 1676, 4569, 4570, 4050, 8064, 1489, 8065, 3497, 8066, 2614, 2889, # 7942
+ 4051, 8067, 8068, 2966, 8069, 8070, 8071, 8072, 3171, 4571, 4572, 2182, 1722, 8073, 3238, 3239, # 7958
+ 1842, 3610, 1715, 481, 365, 1975, 1856, 8074, 8075, 1962, 2491, 4573, 8076, 2126, 3611, 3240, # 7974
+ 433, 1894, 2063, 2075, 8077, 602, 2741, 8078, 8079, 8080, 8081, 8082, 3014, 1628, 3400, 8083, # 7990
+ 3172, 4574, 4052, 2890, 4575, 2512, 8084, 2544, 2772, 8085, 8086, 8087, 3310, 4576, 2891, 8088, # 8006
+ 4577, 8089, 2851, 4578, 4579, 1221, 2967, 4053, 2513, 8090, 8091, 8092, 1867, 1989, 8093, 8094, # 8022
+ 8095, 1895, 8096, 8097, 4580, 1896, 4054, 318, 8098, 2094, 4055, 4293, 8099, 8100, 485, 8101, # 8038
+ 938, 3862, 553, 2670, 116, 8102, 3863, 3612, 8103, 3498, 2671, 2773, 3401, 3311, 2807, 8104, # 8054
+ 3613, 2929, 4056, 1747, 2930, 2968, 8105, 8106, 207, 8107, 8108, 2672, 4581, 2514, 8109, 3015, # 8070
+ 890, 3614, 3864, 8110, 1877, 3732, 3402, 8111, 2183, 2353, 3403, 1652, 8112, 8113, 8114, 941, # 8086
+ 2294, 208, 3499, 4057, 2019, 330, 4294, 3865, 2892, 2492, 3733, 4295, 8115, 8116, 8117, 8118, # 8102
)
-
+# fmt: on
diff --git a/src/pip/_vendor/chardet/euctwprober.py b/src/pip/_vendor/chardet/euctwprober.py
index 35669cc4d..a37ab1899 100644
--- a/src/pip/_vendor/chardet/euctwprober.py
+++ b/src/pip/_vendor/chardet/euctwprober.py
@@ -25,22 +25,23 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
-from .mbcharsetprober import MultiByteCharSetProber
-from .codingstatemachine import CodingStateMachine
from .chardistribution import EUCTWDistributionAnalysis
+from .codingstatemachine import CodingStateMachine
+from .mbcharsetprober import MultiByteCharSetProber
from .mbcssm import EUCTW_SM_MODEL
+
class EUCTWProber(MultiByteCharSetProber):
- def __init__(self):
- super(EUCTWProber, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
self.coding_sm = CodingStateMachine(EUCTW_SM_MODEL)
self.distribution_analyzer = EUCTWDistributionAnalysis()
self.reset()
@property
- def charset_name(self):
+ def charset_name(self) -> str:
return "EUC-TW"
@property
- def language(self):
+ def language(self) -> str:
return "Taiwan"
diff --git a/src/pip/_vendor/chardet/gb2312freq.py b/src/pip/_vendor/chardet/gb2312freq.py
index 697837bd9..b32bfc742 100644
--- a/src/pip/_vendor/chardet/gb2312freq.py
+++ b/src/pip/_vendor/chardet/gb2312freq.py
@@ -43,6 +43,7 @@ GB2312_TYPICAL_DISTRIBUTION_RATIO = 0.9
GB2312_TABLE_SIZE = 3760
+# fmt: off
GB2312_CHAR_TO_FREQ_ORDER = (
1671, 749,1443,2364,3924,3807,2330,3921,1704,3463,2691,1511,1515, 572,3191,2205,
2361, 224,2558, 479,1711, 963,3162, 440,4060,1905,2966,2947,3580,2647,3961,3842,
@@ -280,4 +281,4 @@ GB2312_CHAR_TO_FREQ_ORDER = (
381,1638,4592,1020, 516,3214, 458, 947,4575,1432, 211,1514,2926,1865,2142, 189,
852,1221,1400,1486, 882,2299,4036, 351, 28,1122, 700,6479,6480,6481,6482,6483, #last 512
)
-
+# fmt: on
diff --git a/src/pip/_vendor/chardet/gb2312prober.py b/src/pip/_vendor/chardet/gb2312prober.py
index 8446d2dd9..d423e7311 100644
--- a/src/pip/_vendor/chardet/gb2312prober.py
+++ b/src/pip/_vendor/chardet/gb2312prober.py
@@ -25,22 +25,23 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
-from .mbcharsetprober import MultiByteCharSetProber
-from .codingstatemachine import CodingStateMachine
from .chardistribution import GB2312DistributionAnalysis
+from .codingstatemachine import CodingStateMachine
+from .mbcharsetprober import MultiByteCharSetProber
from .mbcssm import GB2312_SM_MODEL
+
class GB2312Prober(MultiByteCharSetProber):
- def __init__(self):
- super(GB2312Prober, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
self.coding_sm = CodingStateMachine(GB2312_SM_MODEL)
self.distribution_analyzer = GB2312DistributionAnalysis()
self.reset()
@property
- def charset_name(self):
+ def charset_name(self) -> str:
return "GB2312"
@property
- def language(self):
+ def language(self) -> str:
return "Chinese"
diff --git a/src/pip/_vendor/chardet/hebrewprober.py b/src/pip/_vendor/chardet/hebrewprober.py
index b0e1bf492..785d0057b 100644
--- a/src/pip/_vendor/chardet/hebrewprober.py
+++ b/src/pip/_vendor/chardet/hebrewprober.py
@@ -25,8 +25,11 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
+from typing import Optional, Union
+
from .charsetprober import CharSetProber
from .enums import ProbingState
+from .sbcharsetprober import SingleByteCharSetProber
# This prober doesn't actually recognize a language or a charset.
# It is a helper prober for the use of the Hebrew model probers
@@ -125,18 +128,20 @@ from .enums import ProbingState
# model probers scores. The answer is returned in the form of the name of the
# charset identified, either "windows-1255" or "ISO-8859-8".
+
class HebrewProber(CharSetProber):
+ SPACE = 0x20
# windows-1255 / ISO-8859-8 code points of interest
- FINAL_KAF = 0xea
- NORMAL_KAF = 0xeb
- FINAL_MEM = 0xed
- NORMAL_MEM = 0xee
- FINAL_NUN = 0xef
- NORMAL_NUN = 0xf0
- FINAL_PE = 0xf3
- NORMAL_PE = 0xf4
- FINAL_TSADI = 0xf5
- NORMAL_TSADI = 0xf6
+ FINAL_KAF = 0xEA
+ NORMAL_KAF = 0xEB
+ FINAL_MEM = 0xED
+ NORMAL_MEM = 0xEE
+ FINAL_NUN = 0xEF
+ NORMAL_NUN = 0xF0
+ FINAL_PE = 0xF3
+ NORMAL_PE = 0xF4
+ FINAL_TSADI = 0xF5
+ NORMAL_TSADI = 0xF6
# Minimum Visual vs Logical final letter score difference.
# If the difference is below this, don't rely solely on the final letter score
@@ -151,35 +156,44 @@ class HebrewProber(CharSetProber):
VISUAL_HEBREW_NAME = "ISO-8859-8"
LOGICAL_HEBREW_NAME = "windows-1255"
- def __init__(self):
- super(HebrewProber, self).__init__()
- self._final_char_logical_score = None
- self._final_char_visual_score = None
- self._prev = None
- self._before_prev = None
- self._logical_prober = None
- self._visual_prober = None
+ def __init__(self) -> None:
+ super().__init__()
+ self._final_char_logical_score = 0
+ self._final_char_visual_score = 0
+ self._prev = self.SPACE
+ self._before_prev = self.SPACE
+ self._logical_prober: Optional[SingleByteCharSetProber] = None
+ self._visual_prober: Optional[SingleByteCharSetProber] = None
self.reset()
- def reset(self):
+ def reset(self) -> None:
self._final_char_logical_score = 0
self._final_char_visual_score = 0
# The two last characters seen in the previous buffer,
# mPrev and mBeforePrev are initialized to space in order to simulate
# a word delimiter at the beginning of the data
- self._prev = ' '
- self._before_prev = ' '
+ self._prev = self.SPACE
+ self._before_prev = self.SPACE
# These probers are owned by the group prober.
- def set_model_probers(self, logicalProber, visualProber):
- self._logical_prober = logicalProber
- self._visual_prober = visualProber
+ def set_model_probers(
+ self,
+ logical_prober: SingleByteCharSetProber,
+ visual_prober: SingleByteCharSetProber,
+ ) -> None:
+ self._logical_prober = logical_prober
+ self._visual_prober = visual_prober
- def is_final(self, c):
- return c in [self.FINAL_KAF, self.FINAL_MEM, self.FINAL_NUN,
- self.FINAL_PE, self.FINAL_TSADI]
+ def is_final(self, c: int) -> bool:
+ return c in [
+ self.FINAL_KAF,
+ self.FINAL_MEM,
+ self.FINAL_NUN,
+ self.FINAL_PE,
+ self.FINAL_TSADI,
+ ]
- def is_non_final(self, c):
+ def is_non_final(self, c: int) -> bool:
# The normal Tsadi is not a good Non-Final letter due to words like
# 'lechotet' (to chat) containing an apostrophe after the tsadi. This
# apostrophe is converted to a space in FilterWithoutEnglishLetters
@@ -190,10 +204,9 @@ class HebrewProber(CharSetProber):
# for example legally end with a Non-Final Pe or Kaf. However, the
# benefit of these letters as Non-Final letters outweighs the damage
# since these words are quite rare.
- return c in [self.NORMAL_KAF, self.NORMAL_MEM,
- self.NORMAL_NUN, self.NORMAL_PE]
+ return c in [self.NORMAL_KAF, self.NORMAL_MEM, self.NORMAL_NUN, self.NORMAL_PE]
- def feed(self, byte_str):
+ def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState:
# Final letter analysis for logical-visual decision.
# Look for evidence that the received buffer is either logical Hebrew
# or visual Hebrew.
@@ -227,9 +240,9 @@ class HebrewProber(CharSetProber):
byte_str = self.filter_high_byte_only(byte_str)
for cur in byte_str:
- if cur == ' ':
+ if cur == self.SPACE:
# We stand on a space - a word just ended
- if self._before_prev != ' ':
+ if self._before_prev != self.SPACE:
# next-to-last char was not a space so self._prev is not a
# 1 letter word
if self.is_final(self._prev):
@@ -241,8 +254,11 @@ class HebrewProber(CharSetProber):
self._final_char_visual_score += 1
else:
# Not standing on a space
- if ((self._before_prev == ' ') and
- (self.is_final(self._prev)) and (cur != ' ')):
+ if (
+ (self._before_prev == self.SPACE)
+ and (self.is_final(self._prev))
+ and (cur != self.SPACE)
+ ):
# case (3) [-2:space][-1:final letter][cur:not space]
self._final_char_visual_score += 1
self._before_prev = self._prev
@@ -253,7 +269,10 @@ class HebrewProber(CharSetProber):
return ProbingState.DETECTING
@property
- def charset_name(self):
+ def charset_name(self) -> str:
+ assert self._logical_prober is not None
+ assert self._visual_prober is not None
+
# Make the decision: is it Logical or Visual?
# If the final letter score distance is dominant enough, rely on it.
finalsub = self._final_char_logical_score - self._final_char_visual_score
@@ -263,8 +282,9 @@ class HebrewProber(CharSetProber):
return self.VISUAL_HEBREW_NAME
# It's not dominant enough, try to rely on the model scores instead.
- modelsub = (self._logical_prober.get_confidence()
- - self._visual_prober.get_confidence())
+ modelsub = (
+ self._logical_prober.get_confidence() - self._visual_prober.get_confidence()
+ )
if modelsub > self.MIN_MODEL_DISTANCE:
return self.LOGICAL_HEBREW_NAME
if modelsub < -self.MIN_MODEL_DISTANCE:
@@ -280,13 +300,17 @@ class HebrewProber(CharSetProber):
return self.LOGICAL_HEBREW_NAME
@property
- def language(self):
- return 'Hebrew'
+ def language(self) -> str:
+ return "Hebrew"
@property
- def state(self):
+ def state(self) -> ProbingState:
+ assert self._logical_prober is not None
+ assert self._visual_prober is not None
+
# Remain active as long as any of the model probers are active.
- if (self._logical_prober.state == ProbingState.NOT_ME) and \
- (self._visual_prober.state == ProbingState.NOT_ME):
+ if (self._logical_prober.state == ProbingState.NOT_ME) and (
+ self._visual_prober.state == ProbingState.NOT_ME
+ ):
return ProbingState.NOT_ME
return ProbingState.DETECTING
diff --git a/src/pip/_vendor/chardet/jisfreq.py b/src/pip/_vendor/chardet/jisfreq.py
index 83fc082b5..3293576e0 100644
--- a/src/pip/_vendor/chardet/jisfreq.py
+++ b/src/pip/_vendor/chardet/jisfreq.py
@@ -46,6 +46,7 @@ JIS_TYPICAL_DISTRIBUTION_RATIO = 3.0
# Char to FreqOrder table ,
JIS_TABLE_SIZE = 4368
+# fmt: off
JIS_CHAR_TO_FREQ_ORDER = (
40, 1, 6, 182, 152, 180, 295,2127, 285, 381,3295,4304,3068,4606,3165,3510, # 16
3511,1822,2785,4607,1193,2226,5070,4608, 171,2996,1247, 18, 179,5071, 856,1661, # 32
@@ -321,5 +322,4 @@ JIS_CHAR_TO_FREQ_ORDER = (
1444,1698,2385,2251,3729,1365,2281,2235,1717,6188, 864,3841,2515, 444, 527,2767, # 4352
2922,3625, 544, 461,6189, 566, 209,2437,3398,2098,1065,2068,3331,3626,3257,2137, # 4368 #last 512
)
-
-
+# fmt: on
diff --git a/src/pip/_vendor/chardet/johabfreq.py b/src/pip/_vendor/chardet/johabfreq.py
new file mode 100644
index 000000000..c12969990
--- /dev/null
+++ b/src/pip/_vendor/chardet/johabfreq.py
@@ -0,0 +1,2382 @@
+######################## BEGIN LICENSE BLOCK ########################
+# The Original Code is Mozilla Communicator client code.
+#
+# The Initial Developer of the Original Code is
+# Netscape Communications Corporation.
+# Portions created by the Initial Developer are Copyright (C) 1998
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Mark Pilgrim - port to Python
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+# 02110-1301 USA
+######################### END LICENSE BLOCK #########################
+
+# The frequency data itself is the same as euc-kr.
+# This is just a mapping table to euc-kr.
+
+JOHAB_TO_EUCKR_ORDER_TABLE = {
+ 0x8861: 0,
+ 0x8862: 1,
+ 0x8865: 2,
+ 0x8868: 3,
+ 0x8869: 4,
+ 0x886A: 5,
+ 0x886B: 6,
+ 0x8871: 7,
+ 0x8873: 8,
+ 0x8874: 9,
+ 0x8875: 10,
+ 0x8876: 11,
+ 0x8877: 12,
+ 0x8878: 13,
+ 0x8879: 14,
+ 0x887B: 15,
+ 0x887C: 16,
+ 0x887D: 17,
+ 0x8881: 18,
+ 0x8882: 19,
+ 0x8885: 20,
+ 0x8889: 21,
+ 0x8891: 22,
+ 0x8893: 23,
+ 0x8895: 24,
+ 0x8896: 25,
+ 0x8897: 26,
+ 0x88A1: 27,
+ 0x88A2: 28,
+ 0x88A5: 29,
+ 0x88A9: 30,
+ 0x88B5: 31,
+ 0x88B7: 32,
+ 0x88C1: 33,
+ 0x88C5: 34,
+ 0x88C9: 35,
+ 0x88E1: 36,
+ 0x88E2: 37,
+ 0x88E5: 38,
+ 0x88E8: 39,
+ 0x88E9: 40,
+ 0x88EB: 41,
+ 0x88F1: 42,
+ 0x88F3: 43,
+ 0x88F5: 44,
+ 0x88F6: 45,
+ 0x88F7: 46,
+ 0x88F8: 47,
+ 0x88FB: 48,
+ 0x88FC: 49,
+ 0x88FD: 50,
+ 0x8941: 51,
+ 0x8945: 52,
+ 0x8949: 53,
+ 0x8951: 54,
+ 0x8953: 55,
+ 0x8955: 56,
+ 0x8956: 57,
+ 0x8957: 58,
+ 0x8961: 59,
+ 0x8962: 60,
+ 0x8963: 61,
+ 0x8965: 62,
+ 0x8968: 63,
+ 0x8969: 64,
+ 0x8971: 65,
+ 0x8973: 66,
+ 0x8975: 67,
+ 0x8976: 68,
+ 0x8977: 69,
+ 0x897B: 70,
+ 0x8981: 71,
+ 0x8985: 72,
+ 0x8989: 73,
+ 0x8993: 74,
+ 0x8995: 75,
+ 0x89A1: 76,
+ 0x89A2: 77,
+ 0x89A5: 78,
+ 0x89A8: 79,
+ 0x89A9: 80,
+ 0x89AB: 81,
+ 0x89AD: 82,
+ 0x89B0: 83,
+ 0x89B1: 84,
+ 0x89B3: 85,
+ 0x89B5: 86,
+ 0x89B7: 87,
+ 0x89B8: 88,
+ 0x89C1: 89,
+ 0x89C2: 90,
+ 0x89C5: 91,
+ 0x89C9: 92,
+ 0x89CB: 93,
+ 0x89D1: 94,
+ 0x89D3: 95,
+ 0x89D5: 96,
+ 0x89D7: 97,
+ 0x89E1: 98,
+ 0x89E5: 99,
+ 0x89E9: 100,
+ 0x89F3: 101,
+ 0x89F6: 102,
+ 0x89F7: 103,
+ 0x8A41: 104,
+ 0x8A42: 105,
+ 0x8A45: 106,
+ 0x8A49: 107,
+ 0x8A51: 108,
+ 0x8A53: 109,
+ 0x8A55: 110,
+ 0x8A57: 111,
+ 0x8A61: 112,
+ 0x8A65: 113,
+ 0x8A69: 114,
+ 0x8A73: 115,
+ 0x8A75: 116,
+ 0x8A81: 117,
+ 0x8A82: 118,
+ 0x8A85: 119,
+ 0x8A88: 120,
+ 0x8A89: 121,
+ 0x8A8A: 122,
+ 0x8A8B: 123,
+ 0x8A90: 124,
+ 0x8A91: 125,
+ 0x8A93: 126,
+ 0x8A95: 127,
+ 0x8A97: 128,
+ 0x8A98: 129,
+ 0x8AA1: 130,
+ 0x8AA2: 131,
+ 0x8AA5: 132,
+ 0x8AA9: 133,
+ 0x8AB6: 134,
+ 0x8AB7: 135,
+ 0x8AC1: 136,
+ 0x8AD5: 137,
+ 0x8AE1: 138,
+ 0x8AE2: 139,
+ 0x8AE5: 140,
+ 0x8AE9: 141,
+ 0x8AF1: 142,
+ 0x8AF3: 143,
+ 0x8AF5: 144,
+ 0x8B41: 145,
+ 0x8B45: 146,
+ 0x8B49: 147,
+ 0x8B61: 148,
+ 0x8B62: 149,
+ 0x8B65: 150,
+ 0x8B68: 151,
+ 0x8B69: 152,
+ 0x8B6A: 153,
+ 0x8B71: 154,
+ 0x8B73: 155,
+ 0x8B75: 156,
+ 0x8B77: 157,
+ 0x8B81: 158,
+ 0x8BA1: 159,
+ 0x8BA2: 160,
+ 0x8BA5: 161,
+ 0x8BA8: 162,
+ 0x8BA9: 163,
+ 0x8BAB: 164,
+ 0x8BB1: 165,
+ 0x8BB3: 166,
+ 0x8BB5: 167,
+ 0x8BB7: 168,
+ 0x8BB8: 169,
+ 0x8BBC: 170,
+ 0x8C61: 171,
+ 0x8C62: 172,
+ 0x8C63: 173,
+ 0x8C65: 174,
+ 0x8C69: 175,
+ 0x8C6B: 176,
+ 0x8C71: 177,
+ 0x8C73: 178,
+ 0x8C75: 179,
+ 0x8C76: 180,
+ 0x8C77: 181,
+ 0x8C7B: 182,
+ 0x8C81: 183,
+ 0x8C82: 184,
+ 0x8C85: 185,
+ 0x8C89: 186,
+ 0x8C91: 187,
+ 0x8C93: 188,
+ 0x8C95: 189,
+ 0x8C96: 190,
+ 0x8C97: 191,
+ 0x8CA1: 192,
+ 0x8CA2: 193,
+ 0x8CA9: 194,
+ 0x8CE1: 195,
+ 0x8CE2: 196,
+ 0x8CE3: 197,
+ 0x8CE5: 198,
+ 0x8CE9: 199,
+ 0x8CF1: 200,
+ 0x8CF3: 201,
+ 0x8CF5: 202,
+ 0x8CF6: 203,
+ 0x8CF7: 204,
+ 0x8D41: 205,
+ 0x8D42: 206,
+ 0x8D45: 207,
+ 0x8D51: 208,
+ 0x8D55: 209,
+ 0x8D57: 210,
+ 0x8D61: 211,
+ 0x8D65: 212,
+ 0x8D69: 213,
+ 0x8D75: 214,
+ 0x8D76: 215,
+ 0x8D7B: 216,
+ 0x8D81: 217,
+ 0x8DA1: 218,
+ 0x8DA2: 219,
+ 0x8DA5: 220,
+ 0x8DA7: 221,
+ 0x8DA9: 222,
+ 0x8DB1: 223,
+ 0x8DB3: 224,
+ 0x8DB5: 225,
+ 0x8DB7: 226,
+ 0x8DB8: 227,
+ 0x8DB9: 228,
+ 0x8DC1: 229,
+ 0x8DC2: 230,
+ 0x8DC9: 231,
+ 0x8DD6: 232,
+ 0x8DD7: 233,
+ 0x8DE1: 234,
+ 0x8DE2: 235,
+ 0x8DF7: 236,
+ 0x8E41: 237,
+ 0x8E45: 238,
+ 0x8E49: 239,
+ 0x8E51: 240,
+ 0x8E53: 241,
+ 0x8E57: 242,
+ 0x8E61: 243,
+ 0x8E81: 244,
+ 0x8E82: 245,
+ 0x8E85: 246,
+ 0x8E89: 247,
+ 0x8E90: 248,
+ 0x8E91: 249,
+ 0x8E93: 250,
+ 0x8E95: 251,
+ 0x8E97: 252,
+ 0x8E98: 253,
+ 0x8EA1: 254,
+ 0x8EA9: 255,
+ 0x8EB6: 256,
+ 0x8EB7: 257,
+ 0x8EC1: 258,
+ 0x8EC2: 259,
+ 0x8EC5: 260,
+ 0x8EC9: 261,
+ 0x8ED1: 262,
+ 0x8ED3: 263,
+ 0x8ED6: 264,
+ 0x8EE1: 265,
+ 0x8EE5: 266,
+ 0x8EE9: 267,
+ 0x8EF1: 268,
+ 0x8EF3: 269,
+ 0x8F41: 270,
+ 0x8F61: 271,
+ 0x8F62: 272,
+ 0x8F65: 273,
+ 0x8F67: 274,
+ 0x8F69: 275,
+ 0x8F6B: 276,
+ 0x8F70: 277,
+ 0x8F71: 278,
+ 0x8F73: 279,
+ 0x8F75: 280,
+ 0x8F77: 281,
+ 0x8F7B: 282,
+ 0x8FA1: 283,
+ 0x8FA2: 284,
+ 0x8FA5: 285,
+ 0x8FA9: 286,
+ 0x8FB1: 287,
+ 0x8FB3: 288,
+ 0x8FB5: 289,
+ 0x8FB7: 290,
+ 0x9061: 291,
+ 0x9062: 292,
+ 0x9063: 293,
+ 0x9065: 294,
+ 0x9068: 295,
+ 0x9069: 296,
+ 0x906A: 297,
+ 0x906B: 298,
+ 0x9071: 299,
+ 0x9073: 300,
+ 0x9075: 301,
+ 0x9076: 302,
+ 0x9077: 303,
+ 0x9078: 304,
+ 0x9079: 305,
+ 0x907B: 306,
+ 0x907D: 307,
+ 0x9081: 308,
+ 0x9082: 309,
+ 0x9085: 310,
+ 0x9089: 311,
+ 0x9091: 312,
+ 0x9093: 313,
+ 0x9095: 314,
+ 0x9096: 315,
+ 0x9097: 316,
+ 0x90A1: 317,
+ 0x90A2: 318,
+ 0x90A5: 319,
+ 0x90A9: 320,
+ 0x90B1: 321,
+ 0x90B7: 322,
+ 0x90E1: 323,
+ 0x90E2: 324,
+ 0x90E4: 325,
+ 0x90E5: 326,
+ 0x90E9: 327,
+ 0x90EB: 328,
+ 0x90EC: 329,
+ 0x90F1: 330,
+ 0x90F3: 331,
+ 0x90F5: 332,
+ 0x90F6: 333,
+ 0x90F7: 334,
+ 0x90FD: 335,
+ 0x9141: 336,
+ 0x9142: 337,
+ 0x9145: 338,
+ 0x9149: 339,
+ 0x9151: 340,
+ 0x9153: 341,
+ 0x9155: 342,
+ 0x9156: 343,
+ 0x9157: 344,
+ 0x9161: 345,
+ 0x9162: 346,
+ 0x9165: 347,
+ 0x9169: 348,
+ 0x9171: 349,
+ 0x9173: 350,
+ 0x9176: 351,
+ 0x9177: 352,
+ 0x917A: 353,
+ 0x9181: 354,
+ 0x9185: 355,
+ 0x91A1: 356,
+ 0x91A2: 357,
+ 0x91A5: 358,
+ 0x91A9: 359,
+ 0x91AB: 360,
+ 0x91B1: 361,
+ 0x91B3: 362,
+ 0x91B5: 363,
+ 0x91B7: 364,
+ 0x91BC: 365,
+ 0x91BD: 366,
+ 0x91C1: 367,
+ 0x91C5: 368,
+ 0x91C9: 369,
+ 0x91D6: 370,
+ 0x9241: 371,
+ 0x9245: 372,
+ 0x9249: 373,
+ 0x9251: 374,
+ 0x9253: 375,
+ 0x9255: 376,
+ 0x9261: 377,
+ 0x9262: 378,
+ 0x9265: 379,
+ 0x9269: 380,
+ 0x9273: 381,
+ 0x9275: 382,
+ 0x9277: 383,
+ 0x9281: 384,
+ 0x9282: 385,
+ 0x9285: 386,
+ 0x9288: 387,
+ 0x9289: 388,
+ 0x9291: 389,
+ 0x9293: 390,
+ 0x9295: 391,
+ 0x9297: 392,
+ 0x92A1: 393,
+ 0x92B6: 394,
+ 0x92C1: 395,
+ 0x92E1: 396,
+ 0x92E5: 397,
+ 0x92E9: 398,
+ 0x92F1: 399,
+ 0x92F3: 400,
+ 0x9341: 401,
+ 0x9342: 402,
+ 0x9349: 403,
+ 0x9351: 404,
+ 0x9353: 405,
+ 0x9357: 406,
+ 0x9361: 407,
+ 0x9362: 408,
+ 0x9365: 409,
+ 0x9369: 410,
+ 0x936A: 411,
+ 0x936B: 412,
+ 0x9371: 413,
+ 0x9373: 414,
+ 0x9375: 415,
+ 0x9377: 416,
+ 0x9378: 417,
+ 0x937C: 418,
+ 0x9381: 419,
+ 0x9385: 420,
+ 0x9389: 421,
+ 0x93A1: 422,
+ 0x93A2: 423,
+ 0x93A5: 424,
+ 0x93A9: 425,
+ 0x93AB: 426,
+ 0x93B1: 427,
+ 0x93B3: 428,
+ 0x93B5: 429,
+ 0x93B7: 430,
+ 0x93BC: 431,
+ 0x9461: 432,
+ 0x9462: 433,
+ 0x9463: 434,
+ 0x9465: 435,
+ 0x9468: 436,
+ 0x9469: 437,
+ 0x946A: 438,
+ 0x946B: 439,
+ 0x946C: 440,
+ 0x9470: 441,
+ 0x9471: 442,
+ 0x9473: 443,
+ 0x9475: 444,
+ 0x9476: 445,
+ 0x9477: 446,
+ 0x9478: 447,
+ 0x9479: 448,
+ 0x947D: 449,
+ 0x9481: 450,
+ 0x9482: 451,
+ 0x9485: 452,
+ 0x9489: 453,
+ 0x9491: 454,
+ 0x9493: 455,
+ 0x9495: 456,
+ 0x9496: 457,
+ 0x9497: 458,
+ 0x94A1: 459,
+ 0x94E1: 460,
+ 0x94E2: 461,
+ 0x94E3: 462,
+ 0x94E5: 463,
+ 0x94E8: 464,
+ 0x94E9: 465,
+ 0x94EB: 466,
+ 0x94EC: 467,
+ 0x94F1: 468,
+ 0x94F3: 469,
+ 0x94F5: 470,
+ 0x94F7: 471,
+ 0x94F9: 472,
+ 0x94FC: 473,
+ 0x9541: 474,
+ 0x9542: 475,
+ 0x9545: 476,
+ 0x9549: 477,
+ 0x9551: 478,
+ 0x9553: 479,
+ 0x9555: 480,
+ 0x9556: 481,
+ 0x9557: 482,
+ 0x9561: 483,
+ 0x9565: 484,
+ 0x9569: 485,
+ 0x9576: 486,
+ 0x9577: 487,
+ 0x9581: 488,
+ 0x9585: 489,
+ 0x95A1: 490,
+ 0x95A2: 491,
+ 0x95A5: 492,
+ 0x95A8: 493,
+ 0x95A9: 494,
+ 0x95AB: 495,
+ 0x95AD: 496,
+ 0x95B1: 497,
+ 0x95B3: 498,
+ 0x95B5: 499,
+ 0x95B7: 500,
+ 0x95B9: 501,
+ 0x95BB: 502,
+ 0x95C1: 503,
+ 0x95C5: 504,
+ 0x95C9: 505,
+ 0x95E1: 506,
+ 0x95F6: 507,
+ 0x9641: 508,
+ 0x9645: 509,
+ 0x9649: 510,
+ 0x9651: 511,
+ 0x9653: 512,
+ 0x9655: 513,
+ 0x9661: 514,
+ 0x9681: 515,
+ 0x9682: 516,
+ 0x9685: 517,
+ 0x9689: 518,
+ 0x9691: 519,
+ 0x9693: 520,
+ 0x9695: 521,
+ 0x9697: 522,
+ 0x96A1: 523,
+ 0x96B6: 524,
+ 0x96C1: 525,
+ 0x96D7: 526,
+ 0x96E1: 527,
+ 0x96E5: 528,
+ 0x96E9: 529,
+ 0x96F3: 530,
+ 0x96F5: 531,
+ 0x96F7: 532,
+ 0x9741: 533,
+ 0x9745: 534,
+ 0x9749: 535,
+ 0x9751: 536,
+ 0x9757: 537,
+ 0x9761: 538,
+ 0x9762: 539,
+ 0x9765: 540,
+ 0x9768: 541,
+ 0x9769: 542,
+ 0x976B: 543,
+ 0x9771: 544,
+ 0x9773: 545,
+ 0x9775: 546,
+ 0x9777: 547,
+ 0x9781: 548,
+ 0x97A1: 549,
+ 0x97A2: 550,
+ 0x97A5: 551,
+ 0x97A8: 552,
+ 0x97A9: 553,
+ 0x97B1: 554,
+ 0x97B3: 555,
+ 0x97B5: 556,
+ 0x97B6: 557,
+ 0x97B7: 558,
+ 0x97B8: 559,
+ 0x9861: 560,
+ 0x9862: 561,
+ 0x9865: 562,
+ 0x9869: 563,
+ 0x9871: 564,
+ 0x9873: 565,
+ 0x9875: 566,
+ 0x9876: 567,
+ 0x9877: 568,
+ 0x987D: 569,
+ 0x9881: 570,
+ 0x9882: 571,
+ 0x9885: 572,
+ 0x9889: 573,
+ 0x9891: 574,
+ 0x9893: 575,
+ 0x9895: 576,
+ 0x9896: 577,
+ 0x9897: 578,
+ 0x98E1: 579,
+ 0x98E2: 580,
+ 0x98E5: 581,
+ 0x98E9: 582,
+ 0x98EB: 583,
+ 0x98EC: 584,
+ 0x98F1: 585,
+ 0x98F3: 586,
+ 0x98F5: 587,
+ 0x98F6: 588,
+ 0x98F7: 589,
+ 0x98FD: 590,
+ 0x9941: 591,
+ 0x9942: 592,
+ 0x9945: 593,
+ 0x9949: 594,
+ 0x9951: 595,
+ 0x9953: 596,
+ 0x9955: 597,
+ 0x9956: 598,
+ 0x9957: 599,
+ 0x9961: 600,
+ 0x9976: 601,
+ 0x99A1: 602,
+ 0x99A2: 603,
+ 0x99A5: 604,
+ 0x99A9: 605,
+ 0x99B7: 606,
+ 0x99C1: 607,
+ 0x99C9: 608,
+ 0x99E1: 609,
+ 0x9A41: 610,
+ 0x9A45: 611,
+ 0x9A81: 612,
+ 0x9A82: 613,
+ 0x9A85: 614,
+ 0x9A89: 615,
+ 0x9A90: 616,
+ 0x9A91: 617,
+ 0x9A97: 618,
+ 0x9AC1: 619,
+ 0x9AE1: 620,
+ 0x9AE5: 621,
+ 0x9AE9: 622,
+ 0x9AF1: 623,
+ 0x9AF3: 624,
+ 0x9AF7: 625,
+ 0x9B61: 626,
+ 0x9B62: 627,
+ 0x9B65: 628,
+ 0x9B68: 629,
+ 0x9B69: 630,
+ 0x9B71: 631,
+ 0x9B73: 632,
+ 0x9B75: 633,
+ 0x9B81: 634,
+ 0x9B85: 635,
+ 0x9B89: 636,
+ 0x9B91: 637,
+ 0x9B93: 638,
+ 0x9BA1: 639,
+ 0x9BA5: 640,
+ 0x9BA9: 641,
+ 0x9BB1: 642,
+ 0x9BB3: 643,
+ 0x9BB5: 644,
+ 0x9BB7: 645,
+ 0x9C61: 646,
+ 0x9C62: 647,
+ 0x9C65: 648,
+ 0x9C69: 649,
+ 0x9C71: 650,
+ 0x9C73: 651,
+ 0x9C75: 652,
+ 0x9C76: 653,
+ 0x9C77: 654,
+ 0x9C78: 655,
+ 0x9C7C: 656,
+ 0x9C7D: 657,
+ 0x9C81: 658,
+ 0x9C82: 659,
+ 0x9C85: 660,
+ 0x9C89: 661,
+ 0x9C91: 662,
+ 0x9C93: 663,
+ 0x9C95: 664,
+ 0x9C96: 665,
+ 0x9C97: 666,
+ 0x9CA1: 667,
+ 0x9CA2: 668,
+ 0x9CA5: 669,
+ 0x9CB5: 670,
+ 0x9CB7: 671,
+ 0x9CE1: 672,
+ 0x9CE2: 673,
+ 0x9CE5: 674,
+ 0x9CE9: 675,
+ 0x9CF1: 676,
+ 0x9CF3: 677,
+ 0x9CF5: 678,
+ 0x9CF6: 679,
+ 0x9CF7: 680,
+ 0x9CFD: 681,
+ 0x9D41: 682,
+ 0x9D42: 683,
+ 0x9D45: 684,
+ 0x9D49: 685,
+ 0x9D51: 686,
+ 0x9D53: 687,
+ 0x9D55: 688,
+ 0x9D57: 689,
+ 0x9D61: 690,
+ 0x9D62: 691,
+ 0x9D65: 692,
+ 0x9D69: 693,
+ 0x9D71: 694,
+ 0x9D73: 695,
+ 0x9D75: 696,
+ 0x9D76: 697,
+ 0x9D77: 698,
+ 0x9D81: 699,
+ 0x9D85: 700,
+ 0x9D93: 701,
+ 0x9D95: 702,
+ 0x9DA1: 703,
+ 0x9DA2: 704,
+ 0x9DA5: 705,
+ 0x9DA9: 706,
+ 0x9DB1: 707,
+ 0x9DB3: 708,
+ 0x9DB5: 709,
+ 0x9DB7: 710,
+ 0x9DC1: 711,
+ 0x9DC5: 712,
+ 0x9DD7: 713,
+ 0x9DF6: 714,
+ 0x9E41: 715,
+ 0x9E45: 716,
+ 0x9E49: 717,
+ 0x9E51: 718,
+ 0x9E53: 719,
+ 0x9E55: 720,
+ 0x9E57: 721,
+ 0x9E61: 722,
+ 0x9E65: 723,
+ 0x9E69: 724,
+ 0x9E73: 725,
+ 0x9E75: 726,
+ 0x9E77: 727,
+ 0x9E81: 728,
+ 0x9E82: 729,
+ 0x9E85: 730,
+ 0x9E89: 731,
+ 0x9E91: 732,
+ 0x9E93: 733,
+ 0x9E95: 734,
+ 0x9E97: 735,
+ 0x9EA1: 736,
+ 0x9EB6: 737,
+ 0x9EC1: 738,
+ 0x9EE1: 739,
+ 0x9EE2: 740,
+ 0x9EE5: 741,
+ 0x9EE9: 742,
+ 0x9EF1: 743,
+ 0x9EF5: 744,
+ 0x9EF7: 745,
+ 0x9F41: 746,
+ 0x9F42: 747,
+ 0x9F45: 748,
+ 0x9F49: 749,
+ 0x9F51: 750,
+ 0x9F53: 751,
+ 0x9F55: 752,
+ 0x9F57: 753,
+ 0x9F61: 754,
+ 0x9F62: 755,
+ 0x9F65: 756,
+ 0x9F69: 757,
+ 0x9F71: 758,
+ 0x9F73: 759,
+ 0x9F75: 760,
+ 0x9F77: 761,
+ 0x9F78: 762,
+ 0x9F7B: 763,
+ 0x9F7C: 764,
+ 0x9FA1: 765,
+ 0x9FA2: 766,
+ 0x9FA5: 767,
+ 0x9FA9: 768,
+ 0x9FB1: 769,
+ 0x9FB3: 770,
+ 0x9FB5: 771,
+ 0x9FB7: 772,
+ 0xA061: 773,
+ 0xA062: 774,
+ 0xA065: 775,
+ 0xA067: 776,
+ 0xA068: 777,
+ 0xA069: 778,
+ 0xA06A: 779,
+ 0xA06B: 780,
+ 0xA071: 781,
+ 0xA073: 782,
+ 0xA075: 783,
+ 0xA077: 784,
+ 0xA078: 785,
+ 0xA07B: 786,
+ 0xA07D: 787,
+ 0xA081: 788,
+ 0xA082: 789,
+ 0xA085: 790,
+ 0xA089: 791,
+ 0xA091: 792,
+ 0xA093: 793,
+ 0xA095: 794,
+ 0xA096: 795,
+ 0xA097: 796,
+ 0xA098: 797,
+ 0xA0A1: 798,
+ 0xA0A2: 799,
+ 0xA0A9: 800,
+ 0xA0B7: 801,
+ 0xA0E1: 802,
+ 0xA0E2: 803,
+ 0xA0E5: 804,
+ 0xA0E9: 805,
+ 0xA0EB: 806,
+ 0xA0F1: 807,
+ 0xA0F3: 808,
+ 0xA0F5: 809,
+ 0xA0F7: 810,
+ 0xA0F8: 811,
+ 0xA0FD: 812,
+ 0xA141: 813,
+ 0xA142: 814,
+ 0xA145: 815,
+ 0xA149: 816,
+ 0xA151: 817,
+ 0xA153: 818,
+ 0xA155: 819,
+ 0xA156: 820,
+ 0xA157: 821,
+ 0xA161: 822,
+ 0xA162: 823,
+ 0xA165: 824,
+ 0xA169: 825,
+ 0xA175: 826,
+ 0xA176: 827,
+ 0xA177: 828,
+ 0xA179: 829,
+ 0xA181: 830,
+ 0xA1A1: 831,
+ 0xA1A2: 832,
+ 0xA1A4: 833,
+ 0xA1A5: 834,
+ 0xA1A9: 835,
+ 0xA1AB: 836,
+ 0xA1B1: 837,
+ 0xA1B3: 838,
+ 0xA1B5: 839,
+ 0xA1B7: 840,
+ 0xA1C1: 841,
+ 0xA1C5: 842,
+ 0xA1D6: 843,
+ 0xA1D7: 844,
+ 0xA241: 845,
+ 0xA245: 846,
+ 0xA249: 847,
+ 0xA253: 848,
+ 0xA255: 849,
+ 0xA257: 850,
+ 0xA261: 851,
+ 0xA265: 852,
+ 0xA269: 853,
+ 0xA273: 854,
+ 0xA275: 855,
+ 0xA281: 856,
+ 0xA282: 857,
+ 0xA283: 858,
+ 0xA285: 859,
+ 0xA288: 860,
+ 0xA289: 861,
+ 0xA28A: 862,
+ 0xA28B: 863,
+ 0xA291: 864,
+ 0xA293: 865,
+ 0xA295: 866,
+ 0xA297: 867,
+ 0xA29B: 868,
+ 0xA29D: 869,
+ 0xA2A1: 870,
+ 0xA2A5: 871,
+ 0xA2A9: 872,
+ 0xA2B3: 873,
+ 0xA2B5: 874,
+ 0xA2C1: 875,
+ 0xA2E1: 876,
+ 0xA2E5: 877,
+ 0xA2E9: 878,
+ 0xA341: 879,
+ 0xA345: 880,
+ 0xA349: 881,
+ 0xA351: 882,
+ 0xA355: 883,
+ 0xA361: 884,
+ 0xA365: 885,
+ 0xA369: 886,
+ 0xA371: 887,
+ 0xA375: 888,
+ 0xA3A1: 889,
+ 0xA3A2: 890,
+ 0xA3A5: 891,
+ 0xA3A8: 892,
+ 0xA3A9: 893,
+ 0xA3AB: 894,
+ 0xA3B1: 895,
+ 0xA3B3: 896,
+ 0xA3B5: 897,
+ 0xA3B6: 898,
+ 0xA3B7: 899,
+ 0xA3B9: 900,
+ 0xA3BB: 901,
+ 0xA461: 902,
+ 0xA462: 903,
+ 0xA463: 904,
+ 0xA464: 905,
+ 0xA465: 906,
+ 0xA468: 907,
+ 0xA469: 908,
+ 0xA46A: 909,
+ 0xA46B: 910,
+ 0xA46C: 911,
+ 0xA471: 912,
+ 0xA473: 913,
+ 0xA475: 914,
+ 0xA477: 915,
+ 0xA47B: 916,
+ 0xA481: 917,
+ 0xA482: 918,
+ 0xA485: 919,
+ 0xA489: 920,
+ 0xA491: 921,
+ 0xA493: 922,
+ 0xA495: 923,
+ 0xA496: 924,
+ 0xA497: 925,
+ 0xA49B: 926,
+ 0xA4A1: 927,
+ 0xA4A2: 928,
+ 0xA4A5: 929,
+ 0xA4B3: 930,
+ 0xA4E1: 931,
+ 0xA4E2: 932,
+ 0xA4E5: 933,
+ 0xA4E8: 934,
+ 0xA4E9: 935,
+ 0xA4EB: 936,
+ 0xA4F1: 937,
+ 0xA4F3: 938,
+ 0xA4F5: 939,
+ 0xA4F7: 940,
+ 0xA4F8: 941,
+ 0xA541: 942,
+ 0xA542: 943,
+ 0xA545: 944,
+ 0xA548: 945,
+ 0xA549: 946,
+ 0xA551: 947,
+ 0xA553: 948,
+ 0xA555: 949,
+ 0xA556: 950,
+ 0xA557: 951,
+ 0xA561: 952,
+ 0xA562: 953,
+ 0xA565: 954,
+ 0xA569: 955,
+ 0xA573: 956,
+ 0xA575: 957,
+ 0xA576: 958,
+ 0xA577: 959,
+ 0xA57B: 960,
+ 0xA581: 961,
+ 0xA585: 962,
+ 0xA5A1: 963,
+ 0xA5A2: 964,
+ 0xA5A3: 965,
+ 0xA5A5: 966,
+ 0xA5A9: 967,
+ 0xA5B1: 968,
+ 0xA5B3: 969,
+ 0xA5B5: 970,
+ 0xA5B7: 971,
+ 0xA5C1: 972,
+ 0xA5C5: 973,
+ 0xA5D6: 974,
+ 0xA5E1: 975,
+ 0xA5F6: 976,
+ 0xA641: 977,
+ 0xA642: 978,
+ 0xA645: 979,
+ 0xA649: 980,
+ 0xA651: 981,
+ 0xA653: 982,
+ 0xA661: 983,
+ 0xA665: 984,
+ 0xA681: 985,
+ 0xA682: 986,
+ 0xA685: 987,
+ 0xA688: 988,
+ 0xA689: 989,
+ 0xA68A: 990,
+ 0xA68B: 991,
+ 0xA691: 992,
+ 0xA693: 993,
+ 0xA695: 994,
+ 0xA697: 995,
+ 0xA69B: 996,
+ 0xA69C: 997,
+ 0xA6A1: 998,
+ 0xA6A9: 999,
+ 0xA6B6: 1000,
+ 0xA6C1: 1001,
+ 0xA6E1: 1002,
+ 0xA6E2: 1003,
+ 0xA6E5: 1004,
+ 0xA6E9: 1005,
+ 0xA6F7: 1006,
+ 0xA741: 1007,
+ 0xA745: 1008,
+ 0xA749: 1009,
+ 0xA751: 1010,
+ 0xA755: 1011,
+ 0xA757: 1012,
+ 0xA761: 1013,
+ 0xA762: 1014,
+ 0xA765: 1015,
+ 0xA769: 1016,
+ 0xA771: 1017,
+ 0xA773: 1018,
+ 0xA775: 1019,
+ 0xA7A1: 1020,
+ 0xA7A2: 1021,
+ 0xA7A5: 1022,
+ 0xA7A9: 1023,
+ 0xA7AB: 1024,
+ 0xA7B1: 1025,
+ 0xA7B3: 1026,
+ 0xA7B5: 1027,
+ 0xA7B7: 1028,
+ 0xA7B8: 1029,
+ 0xA7B9: 1030,
+ 0xA861: 1031,
+ 0xA862: 1032,
+ 0xA865: 1033,
+ 0xA869: 1034,
+ 0xA86B: 1035,
+ 0xA871: 1036,
+ 0xA873: 1037,
+ 0xA875: 1038,
+ 0xA876: 1039,
+ 0xA877: 1040,
+ 0xA87D: 1041,
+ 0xA881: 1042,
+ 0xA882: 1043,
+ 0xA885: 1044,
+ 0xA889: 1045,
+ 0xA891: 1046,
+ 0xA893: 1047,
+ 0xA895: 1048,
+ 0xA896: 1049,
+ 0xA897: 1050,
+ 0xA8A1: 1051,
+ 0xA8A2: 1052,
+ 0xA8B1: 1053,
+ 0xA8E1: 1054,
+ 0xA8E2: 1055,
+ 0xA8E5: 1056,
+ 0xA8E8: 1057,
+ 0xA8E9: 1058,
+ 0xA8F1: 1059,
+ 0xA8F5: 1060,
+ 0xA8F6: 1061,
+ 0xA8F7: 1062,
+ 0xA941: 1063,
+ 0xA957: 1064,
+ 0xA961: 1065,
+ 0xA962: 1066,
+ 0xA971: 1067,
+ 0xA973: 1068,
+ 0xA975: 1069,
+ 0xA976: 1070,
+ 0xA977: 1071,
+ 0xA9A1: 1072,
+ 0xA9A2: 1073,
+ 0xA9A5: 1074,
+ 0xA9A9: 1075,
+ 0xA9B1: 1076,
+ 0xA9B3: 1077,
+ 0xA9B7: 1078,
+ 0xAA41: 1079,
+ 0xAA61: 1080,
+ 0xAA77: 1081,
+ 0xAA81: 1082,
+ 0xAA82: 1083,
+ 0xAA85: 1084,
+ 0xAA89: 1085,
+ 0xAA91: 1086,
+ 0xAA95: 1087,
+ 0xAA97: 1088,
+ 0xAB41: 1089,
+ 0xAB57: 1090,
+ 0xAB61: 1091,
+ 0xAB65: 1092,
+ 0xAB69: 1093,
+ 0xAB71: 1094,
+ 0xAB73: 1095,
+ 0xABA1: 1096,
+ 0xABA2: 1097,
+ 0xABA5: 1098,
+ 0xABA9: 1099,
+ 0xABB1: 1100,
+ 0xABB3: 1101,
+ 0xABB5: 1102,
+ 0xABB7: 1103,
+ 0xAC61: 1104,
+ 0xAC62: 1105,
+ 0xAC64: 1106,
+ 0xAC65: 1107,
+ 0xAC68: 1108,
+ 0xAC69: 1109,
+ 0xAC6A: 1110,
+ 0xAC6B: 1111,
+ 0xAC71: 1112,
+ 0xAC73: 1113,
+ 0xAC75: 1114,
+ 0xAC76: 1115,
+ 0xAC77: 1116,
+ 0xAC7B: 1117,
+ 0xAC81: 1118,
+ 0xAC82: 1119,
+ 0xAC85: 1120,
+ 0xAC89: 1121,
+ 0xAC91: 1122,
+ 0xAC93: 1123,
+ 0xAC95: 1124,
+ 0xAC96: 1125,
+ 0xAC97: 1126,
+ 0xACA1: 1127,
+ 0xACA2: 1128,
+ 0xACA5: 1129,
+ 0xACA9: 1130,
+ 0xACB1: 1131,
+ 0xACB3: 1132,
+ 0xACB5: 1133,
+ 0xACB7: 1134,
+ 0xACC1: 1135,
+ 0xACC5: 1136,
+ 0xACC9: 1137,
+ 0xACD1: 1138,
+ 0xACD7: 1139,
+ 0xACE1: 1140,
+ 0xACE2: 1141,
+ 0xACE3: 1142,
+ 0xACE4: 1143,
+ 0xACE5: 1144,
+ 0xACE8: 1145,
+ 0xACE9: 1146,
+ 0xACEB: 1147,
+ 0xACEC: 1148,
+ 0xACF1: 1149,
+ 0xACF3: 1150,
+ 0xACF5: 1151,
+ 0xACF6: 1152,
+ 0xACF7: 1153,
+ 0xACFC: 1154,
+ 0xAD41: 1155,
+ 0xAD42: 1156,
+ 0xAD45: 1157,
+ 0xAD49: 1158,
+ 0xAD51: 1159,
+ 0xAD53: 1160,
+ 0xAD55: 1161,
+ 0xAD56: 1162,
+ 0xAD57: 1163,
+ 0xAD61: 1164,
+ 0xAD62: 1165,
+ 0xAD65: 1166,
+ 0xAD69: 1167,
+ 0xAD71: 1168,
+ 0xAD73: 1169,
+ 0xAD75: 1170,
+ 0xAD76: 1171,
+ 0xAD77: 1172,
+ 0xAD81: 1173,
+ 0xAD85: 1174,
+ 0xAD89: 1175,
+ 0xAD97: 1176,
+ 0xADA1: 1177,
+ 0xADA2: 1178,
+ 0xADA3: 1179,
+ 0xADA5: 1180,
+ 0xADA9: 1181,
+ 0xADAB: 1182,
+ 0xADB1: 1183,
+ 0xADB3: 1184,
+ 0xADB5: 1185,
+ 0xADB7: 1186,
+ 0xADBB: 1187,
+ 0xADC1: 1188,
+ 0xADC2: 1189,
+ 0xADC5: 1190,
+ 0xADC9: 1191,
+ 0xADD7: 1192,
+ 0xADE1: 1193,
+ 0xADE5: 1194,
+ 0xADE9: 1195,
+ 0xADF1: 1196,
+ 0xADF5: 1197,
+ 0xADF6: 1198,
+ 0xAE41: 1199,
+ 0xAE45: 1200,
+ 0xAE49: 1201,
+ 0xAE51: 1202,
+ 0xAE53: 1203,
+ 0xAE55: 1204,
+ 0xAE61: 1205,
+ 0xAE62: 1206,
+ 0xAE65: 1207,
+ 0xAE69: 1208,
+ 0xAE71: 1209,
+ 0xAE73: 1210,
+ 0xAE75: 1211,
+ 0xAE77: 1212,
+ 0xAE81: 1213,
+ 0xAE82: 1214,
+ 0xAE85: 1215,
+ 0xAE88: 1216,
+ 0xAE89: 1217,
+ 0xAE91: 1218,
+ 0xAE93: 1219,
+ 0xAE95: 1220,
+ 0xAE97: 1221,
+ 0xAE99: 1222,
+ 0xAE9B: 1223,
+ 0xAE9C: 1224,
+ 0xAEA1: 1225,
+ 0xAEB6: 1226,
+ 0xAEC1: 1227,
+ 0xAEC2: 1228,
+ 0xAEC5: 1229,
+ 0xAEC9: 1230,
+ 0xAED1: 1231,
+ 0xAED7: 1232,
+ 0xAEE1: 1233,
+ 0xAEE2: 1234,
+ 0xAEE5: 1235,
+ 0xAEE9: 1236,
+ 0xAEF1: 1237,
+ 0xAEF3: 1238,
+ 0xAEF5: 1239,
+ 0xAEF7: 1240,
+ 0xAF41: 1241,
+ 0xAF42: 1242,
+ 0xAF49: 1243,
+ 0xAF51: 1244,
+ 0xAF55: 1245,
+ 0xAF57: 1246,
+ 0xAF61: 1247,
+ 0xAF62: 1248,
+ 0xAF65: 1249,
+ 0xAF69: 1250,
+ 0xAF6A: 1251,
+ 0xAF71: 1252,
+ 0xAF73: 1253,
+ 0xAF75: 1254,
+ 0xAF77: 1255,
+ 0xAFA1: 1256,
+ 0xAFA2: 1257,
+ 0xAFA5: 1258,
+ 0xAFA8: 1259,
+ 0xAFA9: 1260,
+ 0xAFB0: 1261,
+ 0xAFB1: 1262,
+ 0xAFB3: 1263,
+ 0xAFB5: 1264,
+ 0xAFB7: 1265,
+ 0xAFBC: 1266,
+ 0xB061: 1267,
+ 0xB062: 1268,
+ 0xB064: 1269,
+ 0xB065: 1270,
+ 0xB069: 1271,
+ 0xB071: 1272,
+ 0xB073: 1273,
+ 0xB076: 1274,
+ 0xB077: 1275,
+ 0xB07D: 1276,
+ 0xB081: 1277,
+ 0xB082: 1278,
+ 0xB085: 1279,
+ 0xB089: 1280,
+ 0xB091: 1281,
+ 0xB093: 1282,
+ 0xB096: 1283,
+ 0xB097: 1284,
+ 0xB0B7: 1285,
+ 0xB0E1: 1286,
+ 0xB0E2: 1287,
+ 0xB0E5: 1288,
+ 0xB0E9: 1289,
+ 0xB0EB: 1290,
+ 0xB0F1: 1291,
+ 0xB0F3: 1292,
+ 0xB0F6: 1293,
+ 0xB0F7: 1294,
+ 0xB141: 1295,
+ 0xB145: 1296,
+ 0xB149: 1297,
+ 0xB185: 1298,
+ 0xB1A1: 1299,
+ 0xB1A2: 1300,
+ 0xB1A5: 1301,
+ 0xB1A8: 1302,
+ 0xB1A9: 1303,
+ 0xB1AB: 1304,
+ 0xB1B1: 1305,
+ 0xB1B3: 1306,
+ 0xB1B7: 1307,
+ 0xB1C1: 1308,
+ 0xB1C2: 1309,
+ 0xB1C5: 1310,
+ 0xB1D6: 1311,
+ 0xB1E1: 1312,
+ 0xB1F6: 1313,
+ 0xB241: 1314,
+ 0xB245: 1315,
+ 0xB249: 1316,
+ 0xB251: 1317,
+ 0xB253: 1318,
+ 0xB261: 1319,
+ 0xB281: 1320,
+ 0xB282: 1321,
+ 0xB285: 1322,
+ 0xB289: 1323,
+ 0xB291: 1324,
+ 0xB293: 1325,
+ 0xB297: 1326,
+ 0xB2A1: 1327,
+ 0xB2B6: 1328,
+ 0xB2C1: 1329,
+ 0xB2E1: 1330,
+ 0xB2E5: 1331,
+ 0xB357: 1332,
+ 0xB361: 1333,
+ 0xB362: 1334,
+ 0xB365: 1335,
+ 0xB369: 1336,
+ 0xB36B: 1337,
+ 0xB370: 1338,
+ 0xB371: 1339,
+ 0xB373: 1340,
+ 0xB381: 1341,
+ 0xB385: 1342,
+ 0xB389: 1343,
+ 0xB391: 1344,
+ 0xB3A1: 1345,
+ 0xB3A2: 1346,
+ 0xB3A5: 1347,
+ 0xB3A9: 1348,
+ 0xB3B1: 1349,
+ 0xB3B3: 1350,
+ 0xB3B5: 1351,
+ 0xB3B7: 1352,
+ 0xB461: 1353,
+ 0xB462: 1354,
+ 0xB465: 1355,
+ 0xB466: 1356,
+ 0xB467: 1357,
+ 0xB469: 1358,
+ 0xB46A: 1359,
+ 0xB46B: 1360,
+ 0xB470: 1361,
+ 0xB471: 1362,
+ 0xB473: 1363,
+ 0xB475: 1364,
+ 0xB476: 1365,
+ 0xB477: 1366,
+ 0xB47B: 1367,
+ 0xB47C: 1368,
+ 0xB481: 1369,
+ 0xB482: 1370,
+ 0xB485: 1371,
+ 0xB489: 1372,
+ 0xB491: 1373,
+ 0xB493: 1374,
+ 0xB495: 1375,
+ 0xB496: 1376,
+ 0xB497: 1377,
+ 0xB4A1: 1378,
+ 0xB4A2: 1379,
+ 0xB4A5: 1380,
+ 0xB4A9: 1381,
+ 0xB4AC: 1382,
+ 0xB4B1: 1383,
+ 0xB4B3: 1384,
+ 0xB4B5: 1385,
+ 0xB4B7: 1386,
+ 0xB4BB: 1387,
+ 0xB4BD: 1388,
+ 0xB4C1: 1389,
+ 0xB4C5: 1390,
+ 0xB4C9: 1391,
+ 0xB4D3: 1392,
+ 0xB4E1: 1393,
+ 0xB4E2: 1394,
+ 0xB4E5: 1395,
+ 0xB4E6: 1396,
+ 0xB4E8: 1397,
+ 0xB4E9: 1398,
+ 0xB4EA: 1399,
+ 0xB4EB: 1400,
+ 0xB4F1: 1401,
+ 0xB4F3: 1402,
+ 0xB4F4: 1403,
+ 0xB4F5: 1404,
+ 0xB4F6: 1405,
+ 0xB4F7: 1406,
+ 0xB4F8: 1407,
+ 0xB4FA: 1408,
+ 0xB4FC: 1409,
+ 0xB541: 1410,
+ 0xB542: 1411,
+ 0xB545: 1412,
+ 0xB549: 1413,
+ 0xB551: 1414,
+ 0xB553: 1415,
+ 0xB555: 1416,
+ 0xB557: 1417,
+ 0xB561: 1418,
+ 0xB562: 1419,
+ 0xB563: 1420,
+ 0xB565: 1421,
+ 0xB569: 1422,
+ 0xB56B: 1423,
+ 0xB56C: 1424,
+ 0xB571: 1425,
+ 0xB573: 1426,
+ 0xB574: 1427,
+ 0xB575: 1428,
+ 0xB576: 1429,
+ 0xB577: 1430,
+ 0xB57B: 1431,
+ 0xB57C: 1432,
+ 0xB57D: 1433,
+ 0xB581: 1434,
+ 0xB585: 1435,
+ 0xB589: 1436,
+ 0xB591: 1437,
+ 0xB593: 1438,
+ 0xB595: 1439,
+ 0xB596: 1440,
+ 0xB5A1: 1441,
+ 0xB5A2: 1442,
+ 0xB5A5: 1443,
+ 0xB5A9: 1444,
+ 0xB5AA: 1445,
+ 0xB5AB: 1446,
+ 0xB5AD: 1447,
+ 0xB5B0: 1448,
+ 0xB5B1: 1449,
+ 0xB5B3: 1450,
+ 0xB5B5: 1451,
+ 0xB5B7: 1452,
+ 0xB5B9: 1453,
+ 0xB5C1: 1454,
+ 0xB5C2: 1455,
+ 0xB5C5: 1456,
+ 0xB5C9: 1457,
+ 0xB5D1: 1458,
+ 0xB5D3: 1459,
+ 0xB5D5: 1460,
+ 0xB5D6: 1461,
+ 0xB5D7: 1462,
+ 0xB5E1: 1463,
+ 0xB5E2: 1464,
+ 0xB5E5: 1465,
+ 0xB5F1: 1466,
+ 0xB5F5: 1467,
+ 0xB5F7: 1468,
+ 0xB641: 1469,
+ 0xB642: 1470,
+ 0xB645: 1471,
+ 0xB649: 1472,
+ 0xB651: 1473,
+ 0xB653: 1474,
+ 0xB655: 1475,
+ 0xB657: 1476,
+ 0xB661: 1477,
+ 0xB662: 1478,
+ 0xB665: 1479,
+ 0xB669: 1480,
+ 0xB671: 1481,
+ 0xB673: 1482,
+ 0xB675: 1483,
+ 0xB677: 1484,
+ 0xB681: 1485,
+ 0xB682: 1486,
+ 0xB685: 1487,
+ 0xB689: 1488,
+ 0xB68A: 1489,
+ 0xB68B: 1490,
+ 0xB691: 1491,
+ 0xB693: 1492,
+ 0xB695: 1493,
+ 0xB697: 1494,
+ 0xB6A1: 1495,
+ 0xB6A2: 1496,
+ 0xB6A5: 1497,
+ 0xB6A9: 1498,
+ 0xB6B1: 1499,
+ 0xB6B3: 1500,
+ 0xB6B6: 1501,
+ 0xB6B7: 1502,
+ 0xB6C1: 1503,
+ 0xB6C2: 1504,
+ 0xB6C5: 1505,
+ 0xB6C9: 1506,
+ 0xB6D1: 1507,
+ 0xB6D3: 1508,
+ 0xB6D7: 1509,
+ 0xB6E1: 1510,
+ 0xB6E2: 1511,
+ 0xB6E5: 1512,
+ 0xB6E9: 1513,
+ 0xB6F1: 1514,
+ 0xB6F3: 1515,
+ 0xB6F5: 1516,
+ 0xB6F7: 1517,
+ 0xB741: 1518,
+ 0xB742: 1519,
+ 0xB745: 1520,
+ 0xB749: 1521,
+ 0xB751: 1522,
+ 0xB753: 1523,
+ 0xB755: 1524,
+ 0xB757: 1525,
+ 0xB759: 1526,
+ 0xB761: 1527,
+ 0xB762: 1528,
+ 0xB765: 1529,
+ 0xB769: 1530,
+ 0xB76F: 1531,
+ 0xB771: 1532,
+ 0xB773: 1533,
+ 0xB775: 1534,
+ 0xB777: 1535,
+ 0xB778: 1536,
+ 0xB779: 1537,
+ 0xB77A: 1538,
+ 0xB77B: 1539,
+ 0xB77C: 1540,
+ 0xB77D: 1541,
+ 0xB781: 1542,
+ 0xB785: 1543,
+ 0xB789: 1544,
+ 0xB791: 1545,
+ 0xB795: 1546,
+ 0xB7A1: 1547,
+ 0xB7A2: 1548,
+ 0xB7A5: 1549,
+ 0xB7A9: 1550,
+ 0xB7AA: 1551,
+ 0xB7AB: 1552,
+ 0xB7B0: 1553,
+ 0xB7B1: 1554,
+ 0xB7B3: 1555,
+ 0xB7B5: 1556,
+ 0xB7B6: 1557,
+ 0xB7B7: 1558,
+ 0xB7B8: 1559,
+ 0xB7BC: 1560,
+ 0xB861: 1561,
+ 0xB862: 1562,
+ 0xB865: 1563,
+ 0xB867: 1564,
+ 0xB868: 1565,
+ 0xB869: 1566,
+ 0xB86B: 1567,
+ 0xB871: 1568,
+ 0xB873: 1569,
+ 0xB875: 1570,
+ 0xB876: 1571,
+ 0xB877: 1572,
+ 0xB878: 1573,
+ 0xB881: 1574,
+ 0xB882: 1575,
+ 0xB885: 1576,
+ 0xB889: 1577,
+ 0xB891: 1578,
+ 0xB893: 1579,
+ 0xB895: 1580,
+ 0xB896: 1581,
+ 0xB897: 1582,
+ 0xB8A1: 1583,
+ 0xB8A2: 1584,
+ 0xB8A5: 1585,
+ 0xB8A7: 1586,
+ 0xB8A9: 1587,
+ 0xB8B1: 1588,
+ 0xB8B7: 1589,
+ 0xB8C1: 1590,
+ 0xB8C5: 1591,
+ 0xB8C9: 1592,
+ 0xB8E1: 1593,
+ 0xB8E2: 1594,
+ 0xB8E5: 1595,
+ 0xB8E9: 1596,
+ 0xB8EB: 1597,
+ 0xB8F1: 1598,
+ 0xB8F3: 1599,
+ 0xB8F5: 1600,
+ 0xB8F7: 1601,
+ 0xB8F8: 1602,
+ 0xB941: 1603,
+ 0xB942: 1604,
+ 0xB945: 1605,
+ 0xB949: 1606,
+ 0xB951: 1607,
+ 0xB953: 1608,
+ 0xB955: 1609,
+ 0xB957: 1610,
+ 0xB961: 1611,
+ 0xB965: 1612,
+ 0xB969: 1613,
+ 0xB971: 1614,
+ 0xB973: 1615,
+ 0xB976: 1616,
+ 0xB977: 1617,
+ 0xB981: 1618,
+ 0xB9A1: 1619,
+ 0xB9A2: 1620,
+ 0xB9A5: 1621,
+ 0xB9A9: 1622,
+ 0xB9AB: 1623,
+ 0xB9B1: 1624,
+ 0xB9B3: 1625,
+ 0xB9B5: 1626,
+ 0xB9B7: 1627,
+ 0xB9B8: 1628,
+ 0xB9B9: 1629,
+ 0xB9BD: 1630,
+ 0xB9C1: 1631,
+ 0xB9C2: 1632,
+ 0xB9C9: 1633,
+ 0xB9D3: 1634,
+ 0xB9D5: 1635,
+ 0xB9D7: 1636,
+ 0xB9E1: 1637,
+ 0xB9F6: 1638,
+ 0xB9F7: 1639,
+ 0xBA41: 1640,
+ 0xBA45: 1641,
+ 0xBA49: 1642,
+ 0xBA51: 1643,
+ 0xBA53: 1644,
+ 0xBA55: 1645,
+ 0xBA57: 1646,
+ 0xBA61: 1647,
+ 0xBA62: 1648,
+ 0xBA65: 1649,
+ 0xBA77: 1650,
+ 0xBA81: 1651,
+ 0xBA82: 1652,
+ 0xBA85: 1653,
+ 0xBA89: 1654,
+ 0xBA8A: 1655,
+ 0xBA8B: 1656,
+ 0xBA91: 1657,
+ 0xBA93: 1658,
+ 0xBA95: 1659,
+ 0xBA97: 1660,
+ 0xBAA1: 1661,
+ 0xBAB6: 1662,
+ 0xBAC1: 1663,
+ 0xBAE1: 1664,
+ 0xBAE2: 1665,
+ 0xBAE5: 1666,
+ 0xBAE9: 1667,
+ 0xBAF1: 1668,
+ 0xBAF3: 1669,
+ 0xBAF5: 1670,
+ 0xBB41: 1671,
+ 0xBB45: 1672,
+ 0xBB49: 1673,
+ 0xBB51: 1674,
+ 0xBB61: 1675,
+ 0xBB62: 1676,
+ 0xBB65: 1677,
+ 0xBB69: 1678,
+ 0xBB71: 1679,
+ 0xBB73: 1680,
+ 0xBB75: 1681,
+ 0xBB77: 1682,
+ 0xBBA1: 1683,
+ 0xBBA2: 1684,
+ 0xBBA5: 1685,
+ 0xBBA8: 1686,
+ 0xBBA9: 1687,
+ 0xBBAB: 1688,
+ 0xBBB1: 1689,
+ 0xBBB3: 1690,
+ 0xBBB5: 1691,
+ 0xBBB7: 1692,
+ 0xBBB8: 1693,
+ 0xBBBB: 1694,
+ 0xBBBC: 1695,
+ 0xBC61: 1696,
+ 0xBC62: 1697,
+ 0xBC65: 1698,
+ 0xBC67: 1699,
+ 0xBC69: 1700,
+ 0xBC6C: 1701,
+ 0xBC71: 1702,
+ 0xBC73: 1703,
+ 0xBC75: 1704,
+ 0xBC76: 1705,
+ 0xBC77: 1706,
+ 0xBC81: 1707,
+ 0xBC82: 1708,
+ 0xBC85: 1709,
+ 0xBC89: 1710,
+ 0xBC91: 1711,
+ 0xBC93: 1712,
+ 0xBC95: 1713,
+ 0xBC96: 1714,
+ 0xBC97: 1715,
+ 0xBCA1: 1716,
+ 0xBCA5: 1717,
+ 0xBCB7: 1718,
+ 0xBCE1: 1719,
+ 0xBCE2: 1720,
+ 0xBCE5: 1721,
+ 0xBCE9: 1722,
+ 0xBCF1: 1723,
+ 0xBCF3: 1724,
+ 0xBCF5: 1725,
+ 0xBCF6: 1726,
+ 0xBCF7: 1727,
+ 0xBD41: 1728,
+ 0xBD57: 1729,
+ 0xBD61: 1730,
+ 0xBD76: 1731,
+ 0xBDA1: 1732,
+ 0xBDA2: 1733,
+ 0xBDA5: 1734,
+ 0xBDA9: 1735,
+ 0xBDB1: 1736,
+ 0xBDB3: 1737,
+ 0xBDB5: 1738,
+ 0xBDB7: 1739,
+ 0xBDB9: 1740,
+ 0xBDC1: 1741,
+ 0xBDC2: 1742,
+ 0xBDC9: 1743,
+ 0xBDD6: 1744,
+ 0xBDE1: 1745,
+ 0xBDF6: 1746,
+ 0xBE41: 1747,
+ 0xBE45: 1748,
+ 0xBE49: 1749,
+ 0xBE51: 1750,
+ 0xBE53: 1751,
+ 0xBE77: 1752,
+ 0xBE81: 1753,
+ 0xBE82: 1754,
+ 0xBE85: 1755,
+ 0xBE89: 1756,
+ 0xBE91: 1757,
+ 0xBE93: 1758,
+ 0xBE97: 1759,
+ 0xBEA1: 1760,
+ 0xBEB6: 1761,
+ 0xBEB7: 1762,
+ 0xBEE1: 1763,
+ 0xBF41: 1764,
+ 0xBF61: 1765,
+ 0xBF71: 1766,
+ 0xBF75: 1767,
+ 0xBF77: 1768,
+ 0xBFA1: 1769,
+ 0xBFA2: 1770,
+ 0xBFA5: 1771,
+ 0xBFA9: 1772,
+ 0xBFB1: 1773,
+ 0xBFB3: 1774,
+ 0xBFB7: 1775,
+ 0xBFB8: 1776,
+ 0xBFBD: 1777,
+ 0xC061: 1778,
+ 0xC062: 1779,
+ 0xC065: 1780,
+ 0xC067: 1781,
+ 0xC069: 1782,
+ 0xC071: 1783,
+ 0xC073: 1784,
+ 0xC075: 1785,
+ 0xC076: 1786,
+ 0xC077: 1787,
+ 0xC078: 1788,
+ 0xC081: 1789,
+ 0xC082: 1790,
+ 0xC085: 1791,
+ 0xC089: 1792,
+ 0xC091: 1793,
+ 0xC093: 1794,
+ 0xC095: 1795,
+ 0xC096: 1796,
+ 0xC097: 1797,
+ 0xC0A1: 1798,
+ 0xC0A5: 1799,
+ 0xC0A7: 1800,
+ 0xC0A9: 1801,
+ 0xC0B1: 1802,
+ 0xC0B7: 1803,
+ 0xC0E1: 1804,
+ 0xC0E2: 1805,
+ 0xC0E5: 1806,
+ 0xC0E9: 1807,
+ 0xC0F1: 1808,
+ 0xC0F3: 1809,
+ 0xC0F5: 1810,
+ 0xC0F6: 1811,
+ 0xC0F7: 1812,
+ 0xC141: 1813,
+ 0xC142: 1814,
+ 0xC145: 1815,
+ 0xC149: 1816,
+ 0xC151: 1817,
+ 0xC153: 1818,
+ 0xC155: 1819,
+ 0xC157: 1820,
+ 0xC161: 1821,
+ 0xC165: 1822,
+ 0xC176: 1823,
+ 0xC181: 1824,
+ 0xC185: 1825,
+ 0xC197: 1826,
+ 0xC1A1: 1827,
+ 0xC1A2: 1828,
+ 0xC1A5: 1829,
+ 0xC1A9: 1830,
+ 0xC1B1: 1831,
+ 0xC1B3: 1832,
+ 0xC1B5: 1833,
+ 0xC1B7: 1834,
+ 0xC1C1: 1835,
+ 0xC1C5: 1836,
+ 0xC1C9: 1837,
+ 0xC1D7: 1838,
+ 0xC241: 1839,
+ 0xC245: 1840,
+ 0xC249: 1841,
+ 0xC251: 1842,
+ 0xC253: 1843,
+ 0xC255: 1844,
+ 0xC257: 1845,
+ 0xC261: 1846,
+ 0xC271: 1847,
+ 0xC281: 1848,
+ 0xC282: 1849,
+ 0xC285: 1850,
+ 0xC289: 1851,
+ 0xC291: 1852,
+ 0xC293: 1853,
+ 0xC295: 1854,
+ 0xC297: 1855,
+ 0xC2A1: 1856,
+ 0xC2B6: 1857,
+ 0xC2C1: 1858,
+ 0xC2C5: 1859,
+ 0xC2E1: 1860,
+ 0xC2E5: 1861,
+ 0xC2E9: 1862,
+ 0xC2F1: 1863,
+ 0xC2F3: 1864,
+ 0xC2F5: 1865,
+ 0xC2F7: 1866,
+ 0xC341: 1867,
+ 0xC345: 1868,
+ 0xC349: 1869,
+ 0xC351: 1870,
+ 0xC357: 1871,
+ 0xC361: 1872,
+ 0xC362: 1873,
+ 0xC365: 1874,
+ 0xC369: 1875,
+ 0xC371: 1876,
+ 0xC373: 1877,
+ 0xC375: 1878,
+ 0xC377: 1879,
+ 0xC3A1: 1880,
+ 0xC3A2: 1881,
+ 0xC3A5: 1882,
+ 0xC3A8: 1883,
+ 0xC3A9: 1884,
+ 0xC3AA: 1885,
+ 0xC3B1: 1886,
+ 0xC3B3: 1887,
+ 0xC3B5: 1888,
+ 0xC3B7: 1889,
+ 0xC461: 1890,
+ 0xC462: 1891,
+ 0xC465: 1892,
+ 0xC469: 1893,
+ 0xC471: 1894,
+ 0xC473: 1895,
+ 0xC475: 1896,
+ 0xC477: 1897,
+ 0xC481: 1898,
+ 0xC482: 1899,
+ 0xC485: 1900,
+ 0xC489: 1901,
+ 0xC491: 1902,
+ 0xC493: 1903,
+ 0xC495: 1904,
+ 0xC496: 1905,
+ 0xC497: 1906,
+ 0xC4A1: 1907,
+ 0xC4A2: 1908,
+ 0xC4B7: 1909,
+ 0xC4E1: 1910,
+ 0xC4E2: 1911,
+ 0xC4E5: 1912,
+ 0xC4E8: 1913,
+ 0xC4E9: 1914,
+ 0xC4F1: 1915,
+ 0xC4F3: 1916,
+ 0xC4F5: 1917,
+ 0xC4F6: 1918,
+ 0xC4F7: 1919,
+ 0xC541: 1920,
+ 0xC542: 1921,
+ 0xC545: 1922,
+ 0xC549: 1923,
+ 0xC551: 1924,
+ 0xC553: 1925,
+ 0xC555: 1926,
+ 0xC557: 1927,
+ 0xC561: 1928,
+ 0xC565: 1929,
+ 0xC569: 1930,
+ 0xC571: 1931,
+ 0xC573: 1932,
+ 0xC575: 1933,
+ 0xC576: 1934,
+ 0xC577: 1935,
+ 0xC581: 1936,
+ 0xC5A1: 1937,
+ 0xC5A2: 1938,
+ 0xC5A5: 1939,
+ 0xC5A9: 1940,
+ 0xC5B1: 1941,
+ 0xC5B3: 1942,
+ 0xC5B5: 1943,
+ 0xC5B7: 1944,
+ 0xC5C1: 1945,
+ 0xC5C2: 1946,
+ 0xC5C5: 1947,
+ 0xC5C9: 1948,
+ 0xC5D1: 1949,
+ 0xC5D7: 1950,
+ 0xC5E1: 1951,
+ 0xC5F7: 1952,
+ 0xC641: 1953,
+ 0xC649: 1954,
+ 0xC661: 1955,
+ 0xC681: 1956,
+ 0xC682: 1957,
+ 0xC685: 1958,
+ 0xC689: 1959,
+ 0xC691: 1960,
+ 0xC693: 1961,
+ 0xC695: 1962,
+ 0xC697: 1963,
+ 0xC6A1: 1964,
+ 0xC6A5: 1965,
+ 0xC6A9: 1966,
+ 0xC6B7: 1967,
+ 0xC6C1: 1968,
+ 0xC6D7: 1969,
+ 0xC6E1: 1970,
+ 0xC6E2: 1971,
+ 0xC6E5: 1972,
+ 0xC6E9: 1973,
+ 0xC6F1: 1974,
+ 0xC6F3: 1975,
+ 0xC6F5: 1976,
+ 0xC6F7: 1977,
+ 0xC741: 1978,
+ 0xC745: 1979,
+ 0xC749: 1980,
+ 0xC751: 1981,
+ 0xC761: 1982,
+ 0xC762: 1983,
+ 0xC765: 1984,
+ 0xC769: 1985,
+ 0xC771: 1986,
+ 0xC773: 1987,
+ 0xC777: 1988,
+ 0xC7A1: 1989,
+ 0xC7A2: 1990,
+ 0xC7A5: 1991,
+ 0xC7A9: 1992,
+ 0xC7B1: 1993,
+ 0xC7B3: 1994,
+ 0xC7B5: 1995,
+ 0xC7B7: 1996,
+ 0xC861: 1997,
+ 0xC862: 1998,
+ 0xC865: 1999,
+ 0xC869: 2000,
+ 0xC86A: 2001,
+ 0xC871: 2002,
+ 0xC873: 2003,
+ 0xC875: 2004,
+ 0xC876: 2005,
+ 0xC877: 2006,
+ 0xC881: 2007,
+ 0xC882: 2008,
+ 0xC885: 2009,
+ 0xC889: 2010,
+ 0xC891: 2011,
+ 0xC893: 2012,
+ 0xC895: 2013,
+ 0xC896: 2014,
+ 0xC897: 2015,
+ 0xC8A1: 2016,
+ 0xC8B7: 2017,
+ 0xC8E1: 2018,
+ 0xC8E2: 2019,
+ 0xC8E5: 2020,
+ 0xC8E9: 2021,
+ 0xC8EB: 2022,
+ 0xC8F1: 2023,
+ 0xC8F3: 2024,
+ 0xC8F5: 2025,
+ 0xC8F6: 2026,
+ 0xC8F7: 2027,
+ 0xC941: 2028,
+ 0xC942: 2029,
+ 0xC945: 2030,
+ 0xC949: 2031,
+ 0xC951: 2032,
+ 0xC953: 2033,
+ 0xC955: 2034,
+ 0xC957: 2035,
+ 0xC961: 2036,
+ 0xC965: 2037,
+ 0xC976: 2038,
+ 0xC981: 2039,
+ 0xC985: 2040,
+ 0xC9A1: 2041,
+ 0xC9A2: 2042,
+ 0xC9A5: 2043,
+ 0xC9A9: 2044,
+ 0xC9B1: 2045,
+ 0xC9B3: 2046,
+ 0xC9B5: 2047,
+ 0xC9B7: 2048,
+ 0xC9BC: 2049,
+ 0xC9C1: 2050,
+ 0xC9C5: 2051,
+ 0xC9E1: 2052,
+ 0xCA41: 2053,
+ 0xCA45: 2054,
+ 0xCA55: 2055,
+ 0xCA57: 2056,
+ 0xCA61: 2057,
+ 0xCA81: 2058,
+ 0xCA82: 2059,
+ 0xCA85: 2060,
+ 0xCA89: 2061,
+ 0xCA91: 2062,
+ 0xCA93: 2063,
+ 0xCA95: 2064,
+ 0xCA97: 2065,
+ 0xCAA1: 2066,
+ 0xCAB6: 2067,
+ 0xCAC1: 2068,
+ 0xCAE1: 2069,
+ 0xCAE2: 2070,
+ 0xCAE5: 2071,
+ 0xCAE9: 2072,
+ 0xCAF1: 2073,
+ 0xCAF3: 2074,
+ 0xCAF7: 2075,
+ 0xCB41: 2076,
+ 0xCB45: 2077,
+ 0xCB49: 2078,
+ 0xCB51: 2079,
+ 0xCB57: 2080,
+ 0xCB61: 2081,
+ 0xCB62: 2082,
+ 0xCB65: 2083,
+ 0xCB68: 2084,
+ 0xCB69: 2085,
+ 0xCB6B: 2086,
+ 0xCB71: 2087,
+ 0xCB73: 2088,
+ 0xCB75: 2089,
+ 0xCB81: 2090,
+ 0xCB85: 2091,
+ 0xCB89: 2092,
+ 0xCB91: 2093,
+ 0xCB93: 2094,
+ 0xCBA1: 2095,
+ 0xCBA2: 2096,
+ 0xCBA5: 2097,
+ 0xCBA9: 2098,
+ 0xCBB1: 2099,
+ 0xCBB3: 2100,
+ 0xCBB5: 2101,
+ 0xCBB7: 2102,
+ 0xCC61: 2103,
+ 0xCC62: 2104,
+ 0xCC63: 2105,
+ 0xCC65: 2106,
+ 0xCC69: 2107,
+ 0xCC6B: 2108,
+ 0xCC71: 2109,
+ 0xCC73: 2110,
+ 0xCC75: 2111,
+ 0xCC76: 2112,
+ 0xCC77: 2113,
+ 0xCC7B: 2114,
+ 0xCC81: 2115,
+ 0xCC82: 2116,
+ 0xCC85: 2117,
+ 0xCC89: 2118,
+ 0xCC91: 2119,
+ 0xCC93: 2120,
+ 0xCC95: 2121,
+ 0xCC96: 2122,
+ 0xCC97: 2123,
+ 0xCCA1: 2124,
+ 0xCCA2: 2125,
+ 0xCCE1: 2126,
+ 0xCCE2: 2127,
+ 0xCCE5: 2128,
+ 0xCCE9: 2129,
+ 0xCCF1: 2130,
+ 0xCCF3: 2131,
+ 0xCCF5: 2132,
+ 0xCCF6: 2133,
+ 0xCCF7: 2134,
+ 0xCD41: 2135,
+ 0xCD42: 2136,
+ 0xCD45: 2137,
+ 0xCD49: 2138,
+ 0xCD51: 2139,
+ 0xCD53: 2140,
+ 0xCD55: 2141,
+ 0xCD57: 2142,
+ 0xCD61: 2143,
+ 0xCD65: 2144,
+ 0xCD69: 2145,
+ 0xCD71: 2146,
+ 0xCD73: 2147,
+ 0xCD76: 2148,
+ 0xCD77: 2149,
+ 0xCD81: 2150,
+ 0xCD89: 2151,
+ 0xCD93: 2152,
+ 0xCD95: 2153,
+ 0xCDA1: 2154,
+ 0xCDA2: 2155,
+ 0xCDA5: 2156,
+ 0xCDA9: 2157,
+ 0xCDB1: 2158,
+ 0xCDB3: 2159,
+ 0xCDB5: 2160,
+ 0xCDB7: 2161,
+ 0xCDC1: 2162,
+ 0xCDD7: 2163,
+ 0xCE41: 2164,
+ 0xCE45: 2165,
+ 0xCE61: 2166,
+ 0xCE65: 2167,
+ 0xCE69: 2168,
+ 0xCE73: 2169,
+ 0xCE75: 2170,
+ 0xCE81: 2171,
+ 0xCE82: 2172,
+ 0xCE85: 2173,
+ 0xCE88: 2174,
+ 0xCE89: 2175,
+ 0xCE8B: 2176,
+ 0xCE91: 2177,
+ 0xCE93: 2178,
+ 0xCE95: 2179,
+ 0xCE97: 2180,
+ 0xCEA1: 2181,
+ 0xCEB7: 2182,
+ 0xCEE1: 2183,
+ 0xCEE5: 2184,
+ 0xCEE9: 2185,
+ 0xCEF1: 2186,
+ 0xCEF5: 2187,
+ 0xCF41: 2188,
+ 0xCF45: 2189,
+ 0xCF49: 2190,
+ 0xCF51: 2191,
+ 0xCF55: 2192,
+ 0xCF57: 2193,
+ 0xCF61: 2194,
+ 0xCF65: 2195,
+ 0xCF69: 2196,
+ 0xCF71: 2197,
+ 0xCF73: 2198,
+ 0xCF75: 2199,
+ 0xCFA1: 2200,
+ 0xCFA2: 2201,
+ 0xCFA5: 2202,
+ 0xCFA9: 2203,
+ 0xCFB1: 2204,
+ 0xCFB3: 2205,
+ 0xCFB5: 2206,
+ 0xCFB7: 2207,
+ 0xD061: 2208,
+ 0xD062: 2209,
+ 0xD065: 2210,
+ 0xD069: 2211,
+ 0xD06E: 2212,
+ 0xD071: 2213,
+ 0xD073: 2214,
+ 0xD075: 2215,
+ 0xD077: 2216,
+ 0xD081: 2217,
+ 0xD082: 2218,
+ 0xD085: 2219,
+ 0xD089: 2220,
+ 0xD091: 2221,
+ 0xD093: 2222,
+ 0xD095: 2223,
+ 0xD096: 2224,
+ 0xD097: 2225,
+ 0xD0A1: 2226,
+ 0xD0B7: 2227,
+ 0xD0E1: 2228,
+ 0xD0E2: 2229,
+ 0xD0E5: 2230,
+ 0xD0E9: 2231,
+ 0xD0EB: 2232,
+ 0xD0F1: 2233,
+ 0xD0F3: 2234,
+ 0xD0F5: 2235,
+ 0xD0F7: 2236,
+ 0xD141: 2237,
+ 0xD142: 2238,
+ 0xD145: 2239,
+ 0xD149: 2240,
+ 0xD151: 2241,
+ 0xD153: 2242,
+ 0xD155: 2243,
+ 0xD157: 2244,
+ 0xD161: 2245,
+ 0xD162: 2246,
+ 0xD165: 2247,
+ 0xD169: 2248,
+ 0xD171: 2249,
+ 0xD173: 2250,
+ 0xD175: 2251,
+ 0xD176: 2252,
+ 0xD177: 2253,
+ 0xD181: 2254,
+ 0xD185: 2255,
+ 0xD189: 2256,
+ 0xD193: 2257,
+ 0xD1A1: 2258,
+ 0xD1A2: 2259,
+ 0xD1A5: 2260,
+ 0xD1A9: 2261,
+ 0xD1AE: 2262,
+ 0xD1B1: 2263,
+ 0xD1B3: 2264,
+ 0xD1B5: 2265,
+ 0xD1B7: 2266,
+ 0xD1BB: 2267,
+ 0xD1C1: 2268,
+ 0xD1C2: 2269,
+ 0xD1C5: 2270,
+ 0xD1C9: 2271,
+ 0xD1D5: 2272,
+ 0xD1D7: 2273,
+ 0xD1E1: 2274,
+ 0xD1E2: 2275,
+ 0xD1E5: 2276,
+ 0xD1F5: 2277,
+ 0xD1F7: 2278,
+ 0xD241: 2279,
+ 0xD242: 2280,
+ 0xD245: 2281,
+ 0xD249: 2282,
+ 0xD253: 2283,
+ 0xD255: 2284,
+ 0xD257: 2285,
+ 0xD261: 2286,
+ 0xD265: 2287,
+ 0xD269: 2288,
+ 0xD273: 2289,
+ 0xD275: 2290,
+ 0xD281: 2291,
+ 0xD282: 2292,
+ 0xD285: 2293,
+ 0xD289: 2294,
+ 0xD28E: 2295,
+ 0xD291: 2296,
+ 0xD295: 2297,
+ 0xD297: 2298,
+ 0xD2A1: 2299,
+ 0xD2A5: 2300,
+ 0xD2A9: 2301,
+ 0xD2B1: 2302,
+ 0xD2B7: 2303,
+ 0xD2C1: 2304,
+ 0xD2C2: 2305,
+ 0xD2C5: 2306,
+ 0xD2C9: 2307,
+ 0xD2D7: 2308,
+ 0xD2E1: 2309,
+ 0xD2E2: 2310,
+ 0xD2E5: 2311,
+ 0xD2E9: 2312,
+ 0xD2F1: 2313,
+ 0xD2F3: 2314,
+ 0xD2F5: 2315,
+ 0xD2F7: 2316,
+ 0xD341: 2317,
+ 0xD342: 2318,
+ 0xD345: 2319,
+ 0xD349: 2320,
+ 0xD351: 2321,
+ 0xD355: 2322,
+ 0xD357: 2323,
+ 0xD361: 2324,
+ 0xD362: 2325,
+ 0xD365: 2326,
+ 0xD367: 2327,
+ 0xD368: 2328,
+ 0xD369: 2329,
+ 0xD36A: 2330,
+ 0xD371: 2331,
+ 0xD373: 2332,
+ 0xD375: 2333,
+ 0xD377: 2334,
+ 0xD37B: 2335,
+ 0xD381: 2336,
+ 0xD385: 2337,
+ 0xD389: 2338,
+ 0xD391: 2339,
+ 0xD393: 2340,
+ 0xD397: 2341,
+ 0xD3A1: 2342,
+ 0xD3A2: 2343,
+ 0xD3A5: 2344,
+ 0xD3A9: 2345,
+ 0xD3B1: 2346,
+ 0xD3B3: 2347,
+ 0xD3B5: 2348,
+ 0xD3B7: 2349,
+}
diff --git a/src/pip/_vendor/chardet/compat.py b/src/pip/_vendor/chardet/johabprober.py
index 8941572b3..d7364ba61 100644
--- a/src/pip/_vendor/chardet/compat.py
+++ b/src/pip/_vendor/chardet/johabprober.py
@@ -1,7 +1,13 @@
######################## BEGIN LICENSE BLOCK ########################
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Netscape Communications Corporation.
+# Portions created by the Initial Developer are Copyright (C) 1998
+# the Initial Developer. All Rights Reserved.
+#
# Contributor(s):
-# Dan Blanchard
-# Ian Cordasco
+# Mark Pilgrim - port to Python
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -19,18 +25,23 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
-import sys
+from .chardistribution import JOHABDistributionAnalysis
+from .codingstatemachine import CodingStateMachine
+from .mbcharsetprober import MultiByteCharSetProber
+from .mbcssm import JOHAB_SM_MODEL
+
+
+class JOHABProber(MultiByteCharSetProber):
+ def __init__(self) -> None:
+ super().__init__()
+ self.coding_sm = CodingStateMachine(JOHAB_SM_MODEL)
+ self.distribution_analyzer = JOHABDistributionAnalysis()
+ self.reset()
+ @property
+ def charset_name(self) -> str:
+ return "Johab"
-if sys.version_info < (3, 0):
- PY2 = True
- PY3 = False
- string_types = (str, unicode)
- text_type = unicode
- iteritems = dict.iteritems
-else:
- PY2 = False
- PY3 = True
- string_types = (bytes, str)
- text_type = str
- iteritems = dict.items
+ @property
+ def language(self) -> str:
+ return "Korean"
diff --git a/src/pip/_vendor/chardet/jpcntx.py b/src/pip/_vendor/chardet/jpcntx.py
index 20044e4bc..2f53bdda0 100644
--- a/src/pip/_vendor/chardet/jpcntx.py
+++ b/src/pip/_vendor/chardet/jpcntx.py
@@ -25,110 +25,114 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
+from typing import List, Tuple, Union
# This is hiragana 2-char sequence table, the number in each cell represents its frequency category
-jp2CharContext = (
-(0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1),
-(2,4,0,4,0,3,0,4,0,3,4,4,4,2,4,3,3,4,3,2,3,3,4,2,3,3,3,2,4,1,4,3,3,1,5,4,3,4,3,4,3,5,3,0,3,5,4,2,0,3,1,0,3,3,0,3,3,0,1,1,0,4,3,0,3,3,0,4,0,2,0,3,5,5,5,5,4,0,4,1,0,3,4),
-(0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2),
-(0,4,0,5,0,5,0,4,0,4,5,4,4,3,5,3,5,1,5,3,4,3,4,4,3,4,3,3,4,3,5,4,4,3,5,5,3,5,5,5,3,5,5,3,4,5,5,3,1,3,2,0,3,4,0,4,2,0,4,2,1,5,3,2,3,5,0,4,0,2,0,5,4,4,5,4,5,0,4,0,0,4,4),
-(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),
-(0,3,0,4,0,3,0,3,0,4,5,4,3,3,3,3,4,3,5,4,4,3,5,4,4,3,4,3,4,4,4,4,5,3,4,4,3,4,5,5,4,5,5,1,4,5,4,3,0,3,3,1,3,3,0,4,4,0,3,3,1,5,3,3,3,5,0,4,0,3,0,4,4,3,4,3,3,0,4,1,1,3,4),
-(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),
-(0,4,0,3,0,3,0,4,0,3,4,4,3,2,2,1,2,1,3,1,3,3,3,3,3,4,3,1,3,3,5,3,3,0,4,3,0,5,4,3,3,5,4,4,3,4,4,5,0,1,2,0,1,2,0,2,2,0,1,0,0,5,2,2,1,4,0,3,0,1,0,4,4,3,5,4,3,0,2,1,0,4,3),
-(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),
-(0,3,0,5,0,4,0,2,1,4,4,2,4,1,4,2,4,2,4,3,3,3,4,3,3,3,3,1,4,2,3,3,3,1,4,4,1,1,1,4,3,3,2,0,2,4,3,2,0,3,3,0,3,1,1,0,0,0,3,3,0,4,2,2,3,4,0,4,0,3,0,4,4,5,3,4,4,0,3,0,0,1,4),
-(1,4,0,4,0,4,0,4,0,3,5,4,4,3,4,3,5,4,3,3,4,3,5,4,4,4,4,3,4,2,4,3,3,1,5,4,3,2,4,5,4,5,5,4,4,5,4,4,0,3,2,2,3,3,0,4,3,1,3,2,1,4,3,3,4,5,0,3,0,2,0,4,5,5,4,5,4,0,4,0,0,5,4),
-(0,5,0,5,0,4,0,3,0,4,4,3,4,3,3,3,4,0,4,4,4,3,4,3,4,3,3,1,4,2,4,3,4,0,5,4,1,4,5,4,4,5,3,2,4,3,4,3,2,4,1,3,3,3,2,3,2,0,4,3,3,4,3,3,3,4,0,4,0,3,0,4,5,4,4,4,3,0,4,1,0,1,3),
-(0,3,1,4,0,3,0,2,0,3,4,4,3,1,4,2,3,3,4,3,4,3,4,3,4,4,3,2,3,1,5,4,4,1,4,4,3,5,4,4,3,5,5,4,3,4,4,3,1,2,3,1,2,2,0,3,2,0,3,1,0,5,3,3,3,4,3,3,3,3,4,4,4,4,5,4,2,0,3,3,2,4,3),
-(0,2,0,3,0,1,0,1,0,0,3,2,0,0,2,0,1,0,2,1,3,3,3,1,2,3,1,0,1,0,4,2,1,1,3,3,0,4,3,3,1,4,3,3,0,3,3,2,0,0,0,0,1,0,0,2,0,0,0,0,0,4,1,0,2,3,2,2,2,1,3,3,3,4,4,3,2,0,3,1,0,3,3),
-(0,4,0,4,0,3,0,3,0,4,4,4,3,3,3,3,3,3,4,3,4,2,4,3,4,3,3,2,4,3,4,5,4,1,4,5,3,5,4,5,3,5,4,0,3,5,5,3,1,3,3,2,2,3,0,3,4,1,3,3,2,4,3,3,3,4,0,4,0,3,0,4,5,4,4,5,3,0,4,1,0,3,4),
-(0,2,0,3,0,3,0,0,0,2,2,2,1,0,1,0,0,0,3,0,3,0,3,0,1,3,1,0,3,1,3,3,3,1,3,3,3,0,1,3,1,3,4,0,0,3,1,1,0,3,2,0,0,0,0,1,3,0,1,0,0,3,3,2,0,3,0,0,0,0,0,3,4,3,4,3,3,0,3,0,0,2,3),
-(2,3,0,3,0,2,0,1,0,3,3,4,3,1,3,1,1,1,3,1,4,3,4,3,3,3,0,0,3,1,5,4,3,1,4,3,2,5,5,4,4,4,4,3,3,4,4,4,0,2,1,1,3,2,0,1,2,0,0,1,0,4,1,3,3,3,0,3,0,1,0,4,4,4,5,5,3,0,2,0,0,4,4),
-(0,2,0,1,0,3,1,3,0,2,3,3,3,0,3,1,0,0,3,0,3,2,3,1,3,2,1,1,0,0,4,2,1,0,2,3,1,4,3,2,0,4,4,3,1,3,1,3,0,1,0,0,1,0,0,0,1,0,0,0,0,4,1,1,1,2,0,3,0,0,0,3,4,2,4,3,2,0,1,0,0,3,3),
-(0,1,0,4,0,5,0,4,0,2,4,4,2,3,3,2,3,3,5,3,3,3,4,3,4,2,3,0,4,3,3,3,4,1,4,3,2,1,5,5,3,4,5,1,3,5,4,2,0,3,3,0,1,3,0,4,2,0,1,3,1,4,3,3,3,3,0,3,0,1,0,3,4,4,4,5,5,0,3,0,1,4,5),
-(0,2,0,3,0,3,0,0,0,2,3,1,3,0,4,0,1,1,3,0,3,4,3,2,3,1,0,3,3,2,3,1,3,0,2,3,0,2,1,4,1,2,2,0,0,3,3,0,0,2,0,0,0,1,0,0,0,0,2,2,0,3,2,1,3,3,0,2,0,2,0,0,3,3,1,2,4,0,3,0,2,2,3),
-(2,4,0,5,0,4,0,4,0,2,4,4,4,3,4,3,3,3,1,2,4,3,4,3,4,4,5,0,3,3,3,3,2,0,4,3,1,4,3,4,1,4,4,3,3,4,4,3,1,2,3,0,4,2,0,4,1,0,3,3,0,4,3,3,3,4,0,4,0,2,0,3,5,3,4,5,2,0,3,0,0,4,5),
-(0,3,0,4,0,1,0,1,0,1,3,2,2,1,3,0,3,0,2,0,2,0,3,0,2,0,0,0,1,0,1,1,0,0,3,1,0,0,0,4,0,3,1,0,2,1,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,4,2,2,3,1,0,3,0,0,0,1,4,4,4,3,0,0,4,0,0,1,4),
-(1,4,1,5,0,3,0,3,0,4,5,4,4,3,5,3,3,4,4,3,4,1,3,3,3,3,2,1,4,1,5,4,3,1,4,4,3,5,4,4,3,5,4,3,3,4,4,4,0,3,3,1,2,3,0,3,1,0,3,3,0,5,4,4,4,4,4,4,3,3,5,4,4,3,3,5,4,0,3,2,0,4,4),
-(0,2,0,3,0,1,0,0,0,1,3,3,3,2,4,1,3,0,3,1,3,0,2,2,1,1,0,0,2,0,4,3,1,0,4,3,0,4,4,4,1,4,3,1,1,3,3,1,0,2,0,0,1,3,0,0,0,0,2,0,0,4,3,2,4,3,5,4,3,3,3,4,3,3,4,3,3,0,2,1,0,3,3),
-(0,2,0,4,0,3,0,2,0,2,5,5,3,4,4,4,4,1,4,3,3,0,4,3,4,3,1,3,3,2,4,3,0,3,4,3,0,3,4,4,2,4,4,0,4,5,3,3,2,2,1,1,1,2,0,1,5,0,3,3,2,4,3,3,3,4,0,3,0,2,0,4,4,3,5,5,0,0,3,0,2,3,3),
-(0,3,0,4,0,3,0,1,0,3,4,3,3,1,3,3,3,0,3,1,3,0,4,3,3,1,1,0,3,0,3,3,0,0,4,4,0,1,5,4,3,3,5,0,3,3,4,3,0,2,0,1,1,1,0,1,3,0,1,2,1,3,3,2,3,3,0,3,0,1,0,1,3,3,4,4,1,0,1,2,2,1,3),
-(0,1,0,4,0,4,0,3,0,1,3,3,3,2,3,1,1,0,3,0,3,3,4,3,2,4,2,0,1,0,4,3,2,0,4,3,0,5,3,3,2,4,4,4,3,3,3,4,0,1,3,0,0,1,0,0,1,0,0,0,0,4,2,3,3,3,0,3,0,0,0,4,4,4,5,3,2,0,3,3,0,3,5),
-(0,2,0,3,0,0,0,3,0,1,3,0,2,0,0,0,1,0,3,1,1,3,3,0,0,3,0,0,3,0,2,3,1,0,3,1,0,3,3,2,0,4,2,2,0,2,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,2,1,2,0,1,0,1,0,0,0,1,3,1,2,0,0,0,1,0,0,1,4),
-(0,3,0,3,0,5,0,1,0,2,4,3,1,3,3,2,1,1,5,2,1,0,5,1,2,0,0,0,3,3,2,2,3,2,4,3,0,0,3,3,1,3,3,0,2,5,3,4,0,3,3,0,1,2,0,2,2,0,3,2,0,2,2,3,3,3,0,2,0,1,0,3,4,4,2,5,4,0,3,0,0,3,5),
-(0,3,0,3,0,3,0,1,0,3,3,3,3,0,3,0,2,0,2,1,1,0,2,0,1,0,0,0,2,1,0,0,1,0,3,2,0,0,3,3,1,2,3,1,0,3,3,0,0,1,0,0,0,0,0,2,0,0,0,0,0,2,3,1,2,3,0,3,0,1,0,3,2,1,0,4,3,0,1,1,0,3,3),
-(0,4,0,5,0,3,0,3,0,4,5,5,4,3,5,3,4,3,5,3,3,2,5,3,4,4,4,3,4,3,4,5,5,3,4,4,3,4,4,5,4,4,4,3,4,5,5,4,2,3,4,2,3,4,0,3,3,1,4,3,2,4,3,3,5,5,0,3,0,3,0,5,5,5,5,4,4,0,4,0,1,4,4),
-(0,4,0,4,0,3,0,3,0,3,5,4,4,2,3,2,5,1,3,2,5,1,4,2,3,2,3,3,4,3,3,3,3,2,5,4,1,3,3,5,3,4,4,0,4,4,3,1,1,3,1,0,2,3,0,2,3,0,3,0,0,4,3,1,3,4,0,3,0,2,0,4,4,4,3,4,5,0,4,0,0,3,4),
-(0,3,0,3,0,3,1,2,0,3,4,4,3,3,3,0,2,2,4,3,3,1,3,3,3,1,1,0,3,1,4,3,2,3,4,4,2,4,4,4,3,4,4,3,2,4,4,3,1,3,3,1,3,3,0,4,1,0,2,2,1,4,3,2,3,3,5,4,3,3,5,4,4,3,3,0,4,0,3,2,2,4,4),
-(0,2,0,1,0,0,0,0,0,1,2,1,3,0,0,0,0,0,2,0,1,2,1,0,0,1,0,0,0,0,3,0,0,1,0,1,1,3,1,0,0,0,1,1,0,1,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,1,2,2,0,3,4,0,0,0,1,1,0,0,1,0,0,0,0,0,1,1),
-(0,1,0,0,0,1,0,0,0,0,4,0,4,1,4,0,3,0,4,0,3,0,4,0,3,0,3,0,4,1,5,1,4,0,0,3,0,5,0,5,2,0,1,0,0,0,2,1,4,0,1,3,0,0,3,0,0,3,1,1,4,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0),
-(1,4,0,5,0,3,0,2,0,3,5,4,4,3,4,3,5,3,4,3,3,0,4,3,3,3,3,3,3,2,4,4,3,1,3,4,4,5,4,4,3,4,4,1,3,5,4,3,3,3,1,2,2,3,3,1,3,1,3,3,3,5,3,3,4,5,0,3,0,3,0,3,4,3,4,4,3,0,3,0,2,4,3),
-(0,1,0,4,0,0,0,0,0,1,4,0,4,1,4,2,4,0,3,0,1,0,1,0,0,0,0,0,2,0,3,1,1,1,0,3,0,0,0,1,2,1,0,0,1,1,1,1,0,1,0,0,0,1,0,0,3,0,0,0,0,3,2,0,2,2,0,1,0,0,0,2,3,2,3,3,0,0,0,0,2,1,0),
-(0,5,1,5,0,3,0,3,0,5,4,4,5,1,5,3,3,0,4,3,4,3,5,3,4,3,3,2,4,3,4,3,3,0,3,3,1,4,4,3,4,4,4,3,4,5,5,3,2,3,1,1,3,3,1,3,1,1,3,3,2,4,5,3,3,5,0,4,0,3,0,4,4,3,5,3,3,0,3,4,0,4,3),
-(0,5,0,5,0,3,0,2,0,4,4,3,5,2,4,3,3,3,4,4,4,3,5,3,5,3,3,1,4,0,4,3,3,0,3,3,0,4,4,4,4,5,4,3,3,5,5,3,2,3,1,2,3,2,0,1,0,0,3,2,2,4,4,3,1,5,0,4,0,3,0,4,3,1,3,2,1,0,3,3,0,3,3),
-(0,4,0,5,0,5,0,4,0,4,5,5,5,3,4,3,3,2,5,4,4,3,5,3,5,3,4,0,4,3,4,4,3,2,4,4,3,4,5,4,4,5,5,0,3,5,5,4,1,3,3,2,3,3,1,3,1,0,4,3,1,4,4,3,4,5,0,4,0,2,0,4,3,4,4,3,3,0,4,0,0,5,5),
-(0,4,0,4,0,5,0,1,1,3,3,4,4,3,4,1,3,0,5,1,3,0,3,1,3,1,1,0,3,0,3,3,4,0,4,3,0,4,4,4,3,4,4,0,3,5,4,1,0,3,0,0,2,3,0,3,1,0,3,1,0,3,2,1,3,5,0,3,0,1,0,3,2,3,3,4,4,0,2,2,0,4,4),
-(2,4,0,5,0,4,0,3,0,4,5,5,4,3,5,3,5,3,5,3,5,2,5,3,4,3,3,4,3,4,5,3,2,1,5,4,3,2,3,4,5,3,4,1,2,5,4,3,0,3,3,0,3,2,0,2,3,0,4,1,0,3,4,3,3,5,0,3,0,1,0,4,5,5,5,4,3,0,4,2,0,3,5),
-(0,5,0,4,0,4,0,2,0,5,4,3,4,3,4,3,3,3,4,3,4,2,5,3,5,3,4,1,4,3,4,4,4,0,3,5,0,4,4,4,4,5,3,1,3,4,5,3,3,3,3,3,3,3,0,2,2,0,3,3,2,4,3,3,3,5,3,4,1,3,3,5,3,2,0,0,0,0,4,3,1,3,3),
-(0,1,0,3,0,3,0,1,0,1,3,3,3,2,3,3,3,0,3,0,0,0,3,1,3,0,0,0,2,2,2,3,0,0,3,2,0,1,2,4,1,3,3,0,0,3,3,3,0,1,0,0,2,1,0,0,3,0,3,1,0,3,0,0,1,3,0,2,0,1,0,3,3,1,3,3,0,0,1,1,0,3,3),
-(0,2,0,3,0,2,1,4,0,2,2,3,1,1,3,1,1,0,2,0,3,1,2,3,1,3,0,0,1,0,4,3,2,3,3,3,1,4,2,3,3,3,3,1,0,3,1,4,0,1,1,0,1,2,0,1,1,0,1,1,0,3,1,3,2,2,0,1,0,0,0,2,3,3,3,1,0,0,0,0,0,2,3),
-(0,5,0,4,0,5,0,2,0,4,5,5,3,3,4,3,3,1,5,4,4,2,4,4,4,3,4,2,4,3,5,5,4,3,3,4,3,3,5,5,4,5,5,1,3,4,5,3,1,4,3,1,3,3,0,3,3,1,4,3,1,4,5,3,3,5,0,4,0,3,0,5,3,3,1,4,3,0,4,0,1,5,3),
-(0,5,0,5,0,4,0,2,0,4,4,3,4,3,3,3,3,3,5,4,4,4,4,4,4,5,3,3,5,2,4,4,4,3,4,4,3,3,4,4,5,5,3,3,4,3,4,3,3,4,3,3,3,3,1,2,2,1,4,3,3,5,4,4,3,4,0,4,0,3,0,4,4,4,4,4,1,0,4,2,0,2,4),
-(0,4,0,4,0,3,0,1,0,3,5,2,3,0,3,0,2,1,4,2,3,3,4,1,4,3,3,2,4,1,3,3,3,0,3,3,0,0,3,3,3,5,3,3,3,3,3,2,0,2,0,0,2,0,0,2,0,0,1,0,0,3,1,2,2,3,0,3,0,2,0,4,4,3,3,4,1,0,3,0,0,2,4),
-(0,0,0,4,0,0,0,0,0,0,1,0,1,0,2,0,0,0,0,0,1,0,2,0,1,0,0,0,0,0,3,1,3,0,3,2,0,0,0,1,0,3,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,0,2,0,0,0,0,0,0,2),
-(0,2,1,3,0,2,0,2,0,3,3,3,3,1,3,1,3,3,3,3,3,3,4,2,2,1,2,1,4,0,4,3,1,3,3,3,2,4,3,5,4,3,3,3,3,3,3,3,0,1,3,0,2,0,0,1,0,0,1,0,0,4,2,0,2,3,0,3,3,0,3,3,4,2,3,1,4,0,1,2,0,2,3),
-(0,3,0,3,0,1,0,3,0,2,3,3,3,0,3,1,2,0,3,3,2,3,3,2,3,2,3,1,3,0,4,3,2,0,3,3,1,4,3,3,2,3,4,3,1,3,3,1,1,0,1,1,0,1,0,1,0,1,0,0,0,4,1,1,0,3,0,3,1,0,2,3,3,3,3,3,1,0,0,2,0,3,3),
-(0,0,0,0,0,0,0,0,0,0,3,0,2,0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,3,0,3,0,3,1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,2,0,2,3,0,0,0,0,0,0,0,0,3),
-(0,2,0,3,1,3,0,3,0,2,3,3,3,1,3,1,3,1,3,1,3,3,3,1,3,0,2,3,1,1,4,3,3,2,3,3,1,2,2,4,1,3,3,0,1,4,2,3,0,1,3,0,3,0,0,1,3,0,2,0,0,3,3,2,1,3,0,3,0,2,0,3,4,4,4,3,1,0,3,0,0,3,3),
-(0,2,0,1,0,2,0,0,0,1,3,2,2,1,3,0,1,1,3,0,3,2,3,1,2,0,2,0,1,1,3,3,3,0,3,3,1,1,2,3,2,3,3,1,2,3,2,0,0,1,0,0,0,0,0,0,3,0,1,0,0,2,1,2,1,3,0,3,0,0,0,3,4,4,4,3,2,0,2,0,0,2,4),
-(0,0,0,1,0,1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,2,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,3,1,0,0,0,0,0,0,0,3),
-(0,3,0,3,0,2,0,3,0,3,3,3,2,3,2,2,2,0,3,1,3,3,3,2,3,3,0,0,3,0,3,2,2,0,2,3,1,4,3,4,3,3,2,3,1,5,4,4,0,3,1,2,1,3,0,3,1,1,2,0,2,3,1,3,1,3,0,3,0,1,0,3,3,4,4,2,1,0,2,1,0,2,4),
-(0,1,0,3,0,1,0,2,0,1,4,2,5,1,4,0,2,0,2,1,3,1,4,0,2,1,0,0,2,1,4,1,1,0,3,3,0,5,1,3,2,3,3,1,0,3,2,3,0,1,0,0,0,0,0,0,1,0,0,0,0,4,0,1,0,3,0,2,0,1,0,3,3,3,4,3,3,0,0,0,0,2,3),
-(0,0,0,1,0,0,0,0,0,0,2,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,1,0,0,0,0,0,3),
-(0,1,0,3,0,4,0,3,0,2,4,3,1,0,3,2,2,1,3,1,2,2,3,1,1,1,2,1,3,0,1,2,0,1,3,2,1,3,0,5,5,1,0,0,1,3,2,1,0,3,0,0,1,0,0,0,0,0,3,4,0,1,1,1,3,2,0,2,0,1,0,2,3,3,1,2,3,0,1,0,1,0,4),
-(0,0,0,1,0,3,0,3,0,2,2,1,0,0,4,0,3,0,3,1,3,0,3,0,3,0,1,0,3,0,3,1,3,0,3,3,0,0,1,2,1,1,1,0,1,2,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,2,2,1,2,0,0,2,0,0,0,0,2,3,3,3,3,0,0,0,0,1,4),
-(0,0,0,3,0,3,0,0,0,0,3,1,1,0,3,0,1,0,2,0,1,0,0,0,0,0,0,0,1,0,3,0,2,0,2,3,0,0,2,2,3,1,2,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,2,0,0,0,0,2,3),
-(2,4,0,5,0,5,0,4,0,3,4,3,3,3,4,3,3,3,4,3,4,4,5,4,5,5,5,2,3,0,5,5,4,1,5,4,3,1,5,4,3,4,4,3,3,4,3,3,0,3,2,0,2,3,0,3,0,0,3,3,0,5,3,2,3,3,0,3,0,3,0,3,4,5,4,5,3,0,4,3,0,3,4),
-(0,3,0,3,0,3,0,3,0,3,3,4,3,2,3,2,3,0,4,3,3,3,3,3,3,3,3,0,3,2,4,3,3,1,3,4,3,4,4,4,3,4,4,3,2,4,4,1,0,2,0,0,1,1,0,2,0,0,3,1,0,5,3,2,1,3,0,3,0,1,2,4,3,2,4,3,3,0,3,2,0,4,4),
-(0,3,0,3,0,1,0,0,0,1,4,3,3,2,3,1,3,1,4,2,3,2,4,2,3,4,3,0,2,2,3,3,3,0,3,3,3,0,3,4,1,3,3,0,3,4,3,3,0,1,1,0,1,0,0,0,4,0,3,0,0,3,1,2,1,3,0,4,0,1,0,4,3,3,4,3,3,0,2,0,0,3,3),
-(0,3,0,4,0,1,0,3,0,3,4,3,3,0,3,3,3,1,3,1,3,3,4,3,3,3,0,0,3,1,5,3,3,1,3,3,2,5,4,3,3,4,5,3,2,5,3,4,0,1,0,0,0,0,0,2,0,0,1,1,0,4,2,2,1,3,0,3,0,2,0,4,4,3,5,3,2,0,1,1,0,3,4),
-(0,5,0,4,0,5,0,2,0,4,4,3,3,2,3,3,3,1,4,3,4,1,5,3,4,3,4,0,4,2,4,3,4,1,5,4,0,4,4,4,4,5,4,1,3,5,4,2,1,4,1,1,3,2,0,3,1,0,3,2,1,4,3,3,3,4,0,4,0,3,0,4,4,4,3,3,3,0,4,2,0,3,4),
-(1,4,0,4,0,3,0,1,0,3,3,3,1,1,3,3,2,2,3,3,1,0,3,2,2,1,2,0,3,1,2,1,2,0,3,2,0,2,2,3,3,4,3,0,3,3,1,2,0,1,1,3,1,2,0,0,3,0,1,1,0,3,2,2,3,3,0,3,0,0,0,2,3,3,4,3,3,0,1,0,0,1,4),
-(0,4,0,4,0,4,0,0,0,3,4,4,3,1,4,2,3,2,3,3,3,1,4,3,4,0,3,0,4,2,3,3,2,2,5,4,2,1,3,4,3,4,3,1,3,3,4,2,0,2,1,0,3,3,0,0,2,0,3,1,0,4,4,3,4,3,0,4,0,1,0,2,4,4,4,4,4,0,3,2,0,3,3),
-(0,0,0,1,0,4,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,3,2,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,2),
-(0,2,0,3,0,4,0,4,0,1,3,3,3,0,4,0,2,1,2,1,1,1,2,0,3,1,1,0,1,0,3,1,0,0,3,3,2,0,1,1,0,0,0,0,0,1,0,2,0,2,2,0,3,1,0,0,1,0,1,1,0,1,2,0,3,0,0,0,0,1,0,0,3,3,4,3,1,0,1,0,3,0,2),
-(0,0,0,3,0,5,0,0,0,0,1,0,2,0,3,1,0,1,3,0,0,0,2,0,0,0,1,0,0,0,1,1,0,0,4,0,0,0,2,3,0,1,4,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,1,0,0,0,0,0,0,0,2,0,0,3,0,0,0,0,0,3),
-(0,2,0,5,0,5,0,1,0,2,4,3,3,2,5,1,3,2,3,3,3,0,4,1,2,0,3,0,4,0,2,2,1,1,5,3,0,0,1,4,2,3,2,0,3,3,3,2,0,2,4,1,1,2,0,1,1,0,3,1,0,1,3,1,2,3,0,2,0,0,0,1,3,5,4,4,4,0,3,0,0,1,3),
-(0,4,0,5,0,4,0,4,0,4,5,4,3,3,4,3,3,3,4,3,4,4,5,3,4,5,4,2,4,2,3,4,3,1,4,4,1,3,5,4,4,5,5,4,4,5,5,5,2,3,3,1,4,3,1,3,3,0,3,3,1,4,3,4,4,4,0,3,0,4,0,3,3,4,4,5,0,0,4,3,0,4,5),
-(0,4,0,4,0,3,0,3,0,3,4,4,4,3,3,2,4,3,4,3,4,3,5,3,4,3,2,1,4,2,4,4,3,1,3,4,2,4,5,5,3,4,5,4,1,5,4,3,0,3,2,2,3,2,1,3,1,0,3,3,3,5,3,3,3,5,4,4,2,3,3,4,3,3,3,2,1,0,3,2,1,4,3),
-(0,4,0,5,0,4,0,3,0,3,5,5,3,2,4,3,4,0,5,4,4,1,4,4,4,3,3,3,4,3,5,5,2,3,3,4,1,2,5,5,3,5,5,2,3,5,5,4,0,3,2,0,3,3,1,1,5,1,4,1,0,4,3,2,3,5,0,4,0,3,0,5,4,3,4,3,0,0,4,1,0,4,4),
-(1,3,0,4,0,2,0,2,0,2,5,5,3,3,3,3,3,0,4,2,3,4,4,4,3,4,0,0,3,4,5,4,3,3,3,3,2,5,5,4,5,5,5,4,3,5,5,5,1,3,1,0,1,0,0,3,2,0,4,2,0,5,2,3,2,4,1,3,0,3,0,4,5,4,5,4,3,0,4,2,0,5,4),
-(0,3,0,4,0,5,0,3,0,3,4,4,3,2,3,2,3,3,3,3,3,2,4,3,3,2,2,0,3,3,3,3,3,1,3,3,3,0,4,4,3,4,4,1,1,4,4,2,0,3,1,0,1,1,0,4,1,0,2,3,1,3,3,1,3,4,0,3,0,1,0,3,1,3,0,0,1,0,2,0,0,4,4),
-(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),
-(0,3,0,3,0,2,0,3,0,1,5,4,3,3,3,1,4,2,1,2,3,4,4,2,4,4,5,0,3,1,4,3,4,0,4,3,3,3,2,3,2,5,3,4,3,2,2,3,0,0,3,0,2,1,0,1,2,0,0,0,0,2,1,1,3,1,0,2,0,4,0,3,4,4,4,5,2,0,2,0,0,1,3),
-(0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,1,0,0,1,1,0,0,0,4,2,1,1,0,1,0,3,2,0,0,3,1,1,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,1,0,0,0,2,0,0,0,1,4,0,4,2,1,0,0,0,0,0,1),
-(0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,1,0,1,0,0,0,0,3,1,0,0,0,2,0,2,1,0,0,1,2,1,0,1,1,0,0,3,0,0,0,0,0,0,0,0,0,0,0,1,3,1,0,0,0,0,0,1,0,0,2,1,0,0,0,0,0,0,0,0,2),
-(0,4,0,4,0,4,0,3,0,4,4,3,4,2,4,3,2,0,4,4,4,3,5,3,5,3,3,2,4,2,4,3,4,3,1,4,0,2,3,4,4,4,3,3,3,4,4,4,3,4,1,3,4,3,2,1,2,1,3,3,3,4,4,3,3,5,0,4,0,3,0,4,3,3,3,2,1,0,3,0,0,3,3),
-(0,4,0,3,0,3,0,3,0,3,5,5,3,3,3,3,4,3,4,3,3,3,4,4,4,3,3,3,3,4,3,5,3,3,1,3,2,4,5,5,5,5,4,3,4,5,5,3,2,2,3,3,3,3,2,3,3,1,2,3,2,4,3,3,3,4,0,4,0,2,0,4,3,2,2,1,2,0,3,0,0,4,1),
+# fmt: off
+jp2_char_context = (
+ (0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
+ (2, 4, 0, 4, 0, 3, 0, 4, 0, 3, 4, 4, 4, 2, 4, 3, 3, 4, 3, 2, 3, 3, 4, 2, 3, 3, 3, 2, 4, 1, 4, 3, 3, 1, 5, 4, 3, 4, 3, 4, 3, 5, 3, 0, 3, 5, 4, 2, 0, 3, 1, 0, 3, 3, 0, 3, 3, 0, 1, 1, 0, 4, 3, 0, 3, 3, 0, 4, 0, 2, 0, 3, 5, 5, 5, 5, 4, 0, 4, 1, 0, 3, 4),
+ (0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2),
+ (0, 4, 0, 5, 0, 5, 0, 4, 0, 4, 5, 4, 4, 3, 5, 3, 5, 1, 5, 3, 4, 3, 4, 4, 3, 4, 3, 3, 4, 3, 5, 4, 4, 3, 5, 5, 3, 5, 5, 5, 3, 5, 5, 3, 4, 5, 5, 3, 1, 3, 2, 0, 3, 4, 0, 4, 2, 0, 4, 2, 1, 5, 3, 2, 3, 5, 0, 4, 0, 2, 0, 5, 4, 4, 5, 4, 5, 0, 4, 0, 0, 4, 4),
+ (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ (0, 3, 0, 4, 0, 3, 0, 3, 0, 4, 5, 4, 3, 3, 3, 3, 4, 3, 5, 4, 4, 3, 5, 4, 4, 3, 4, 3, 4, 4, 4, 4, 5, 3, 4, 4, 3, 4, 5, 5, 4, 5, 5, 1, 4, 5, 4, 3, 0, 3, 3, 1, 3, 3, 0, 4, 4, 0, 3, 3, 1, 5, 3, 3, 3, 5, 0, 4, 0, 3, 0, 4, 4, 3, 4, 3, 3, 0, 4, 1, 1, 3, 4),
+ (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ (0, 4, 0, 3, 0, 3, 0, 4, 0, 3, 4, 4, 3, 2, 2, 1, 2, 1, 3, 1, 3, 3, 3, 3, 3, 4, 3, 1, 3, 3, 5, 3, 3, 0, 4, 3, 0, 5, 4, 3, 3, 5, 4, 4, 3, 4, 4, 5, 0, 1, 2, 0, 1, 2, 0, 2, 2, 0, 1, 0, 0, 5, 2, 2, 1, 4, 0, 3, 0, 1, 0, 4, 4, 3, 5, 4, 3, 0, 2, 1, 0, 4, 3),
+ (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ (0, 3, 0, 5, 0, 4, 0, 2, 1, 4, 4, 2, 4, 1, 4, 2, 4, 2, 4, 3, 3, 3, 4, 3, 3, 3, 3, 1, 4, 2, 3, 3, 3, 1, 4, 4, 1, 1, 1, 4, 3, 3, 2, 0, 2, 4, 3, 2, 0, 3, 3, 0, 3, 1, 1, 0, 0, 0, 3, 3, 0, 4, 2, 2, 3, 4, 0, 4, 0, 3, 0, 4, 4, 5, 3, 4, 4, 0, 3, 0, 0, 1, 4),
+ (1, 4, 0, 4, 0, 4, 0, 4, 0, 3, 5, 4, 4, 3, 4, 3, 5, 4, 3, 3, 4, 3, 5, 4, 4, 4, 4, 3, 4, 2, 4, 3, 3, 1, 5, 4, 3, 2, 4, 5, 4, 5, 5, 4, 4, 5, 4, 4, 0, 3, 2, 2, 3, 3, 0, 4, 3, 1, 3, 2, 1, 4, 3, 3, 4, 5, 0, 3, 0, 2, 0, 4, 5, 5, 4, 5, 4, 0, 4, 0, 0, 5, 4),
+ (0, 5, 0, 5, 0, 4, 0, 3, 0, 4, 4, 3, 4, 3, 3, 3, 4, 0, 4, 4, 4, 3, 4, 3, 4, 3, 3, 1, 4, 2, 4, 3, 4, 0, 5, 4, 1, 4, 5, 4, 4, 5, 3, 2, 4, 3, 4, 3, 2, 4, 1, 3, 3, 3, 2, 3, 2, 0, 4, 3, 3, 4, 3, 3, 3, 4, 0, 4, 0, 3, 0, 4, 5, 4, 4, 4, 3, 0, 4, 1, 0, 1, 3),
+ (0, 3, 1, 4, 0, 3, 0, 2, 0, 3, 4, 4, 3, 1, 4, 2, 3, 3, 4, 3, 4, 3, 4, 3, 4, 4, 3, 2, 3, 1, 5, 4, 4, 1, 4, 4, 3, 5, 4, 4, 3, 5, 5, 4, 3, 4, 4, 3, 1, 2, 3, 1, 2, 2, 0, 3, 2, 0, 3, 1, 0, 5, 3, 3, 3, 4, 3, 3, 3, 3, 4, 4, 4, 4, 5, 4, 2, 0, 3, 3, 2, 4, 3),
+ (0, 2, 0, 3, 0, 1, 0, 1, 0, 0, 3, 2, 0, 0, 2, 0, 1, 0, 2, 1, 3, 3, 3, 1, 2, 3, 1, 0, 1, 0, 4, 2, 1, 1, 3, 3, 0, 4, 3, 3, 1, 4, 3, 3, 0, 3, 3, 2, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 0, 0, 0, 4, 1, 0, 2, 3, 2, 2, 2, 1, 3, 3, 3, 4, 4, 3, 2, 0, 3, 1, 0, 3, 3),
+ (0, 4, 0, 4, 0, 3, 0, 3, 0, 4, 4, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 2, 4, 3, 4, 3, 3, 2, 4, 3, 4, 5, 4, 1, 4, 5, 3, 5, 4, 5, 3, 5, 4, 0, 3, 5, 5, 3, 1, 3, 3, 2, 2, 3, 0, 3, 4, 1, 3, 3, 2, 4, 3, 3, 3, 4, 0, 4, 0, 3, 0, 4, 5, 4, 4, 5, 3, 0, 4, 1, 0, 3, 4),
+ (0, 2, 0, 3, 0, 3, 0, 0, 0, 2, 2, 2, 1, 0, 1, 0, 0, 0, 3, 0, 3, 0, 3, 0, 1, 3, 1, 0, 3, 1, 3, 3, 3, 1, 3, 3, 3, 0, 1, 3, 1, 3, 4, 0, 0, 3, 1, 1, 0, 3, 2, 0, 0, 0, 0, 1, 3, 0, 1, 0, 0, 3, 3, 2, 0, 3, 0, 0, 0, 0, 0, 3, 4, 3, 4, 3, 3, 0, 3, 0, 0, 2, 3),
+ (2, 3, 0, 3, 0, 2, 0, 1, 0, 3, 3, 4, 3, 1, 3, 1, 1, 1, 3, 1, 4, 3, 4, 3, 3, 3, 0, 0, 3, 1, 5, 4, 3, 1, 4, 3, 2, 5, 5, 4, 4, 4, 4, 3, 3, 4, 4, 4, 0, 2, 1, 1, 3, 2, 0, 1, 2, 0, 0, 1, 0, 4, 1, 3, 3, 3, 0, 3, 0, 1, 0, 4, 4, 4, 5, 5, 3, 0, 2, 0, 0, 4, 4),
+ (0, 2, 0, 1, 0, 3, 1, 3, 0, 2, 3, 3, 3, 0, 3, 1, 0, 0, 3, 0, 3, 2, 3, 1, 3, 2, 1, 1, 0, 0, 4, 2, 1, 0, 2, 3, 1, 4, 3, 2, 0, 4, 4, 3, 1, 3, 1, 3, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 4, 1, 1, 1, 2, 0, 3, 0, 0, 0, 3, 4, 2, 4, 3, 2, 0, 1, 0, 0, 3, 3),
+ (0, 1, 0, 4, 0, 5, 0, 4, 0, 2, 4, 4, 2, 3, 3, 2, 3, 3, 5, 3, 3, 3, 4, 3, 4, 2, 3, 0, 4, 3, 3, 3, 4, 1, 4, 3, 2, 1, 5, 5, 3, 4, 5, 1, 3, 5, 4, 2, 0, 3, 3, 0, 1, 3, 0, 4, 2, 0, 1, 3, 1, 4, 3, 3, 3, 3, 0, 3, 0, 1, 0, 3, 4, 4, 4, 5, 5, 0, 3, 0, 1, 4, 5),
+ (0, 2, 0, 3, 0, 3, 0, 0, 0, 2, 3, 1, 3, 0, 4, 0, 1, 1, 3, 0, 3, 4, 3, 2, 3, 1, 0, 3, 3, 2, 3, 1, 3, 0, 2, 3, 0, 2, 1, 4, 1, 2, 2, 0, 0, 3, 3, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 2, 2, 0, 3, 2, 1, 3, 3, 0, 2, 0, 2, 0, 0, 3, 3, 1, 2, 4, 0, 3, 0, 2, 2, 3),
+ (2, 4, 0, 5, 0, 4, 0, 4, 0, 2, 4, 4, 4, 3, 4, 3, 3, 3, 1, 2, 4, 3, 4, 3, 4, 4, 5, 0, 3, 3, 3, 3, 2, 0, 4, 3, 1, 4, 3, 4, 1, 4, 4, 3, 3, 4, 4, 3, 1, 2, 3, 0, 4, 2, 0, 4, 1, 0, 3, 3, 0, 4, 3, 3, 3, 4, 0, 4, 0, 2, 0, 3, 5, 3, 4, 5, 2, 0, 3, 0, 0, 4, 5),
+ (0, 3, 0, 4, 0, 1, 0, 1, 0, 1, 3, 2, 2, 1, 3, 0, 3, 0, 2, 0, 2, 0, 3, 0, 2, 0, 0, 0, 1, 0, 1, 1, 0, 0, 3, 1, 0, 0, 0, 4, 0, 3, 1, 0, 2, 1, 3, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 2, 2, 3, 1, 0, 3, 0, 0, 0, 1, 4, 4, 4, 3, 0, 0, 4, 0, 0, 1, 4),
+ (1, 4, 1, 5, 0, 3, 0, 3, 0, 4, 5, 4, 4, 3, 5, 3, 3, 4, 4, 3, 4, 1, 3, 3, 3, 3, 2, 1, 4, 1, 5, 4, 3, 1, 4, 4, 3, 5, 4, 4, 3, 5, 4, 3, 3, 4, 4, 4, 0, 3, 3, 1, 2, 3, 0, 3, 1, 0, 3, 3, 0, 5, 4, 4, 4, 4, 4, 4, 3, 3, 5, 4, 4, 3, 3, 5, 4, 0, 3, 2, 0, 4, 4),
+ (0, 2, 0, 3, 0, 1, 0, 0, 0, 1, 3, 3, 3, 2, 4, 1, 3, 0, 3, 1, 3, 0, 2, 2, 1, 1, 0, 0, 2, 0, 4, 3, 1, 0, 4, 3, 0, 4, 4, 4, 1, 4, 3, 1, 1, 3, 3, 1, 0, 2, 0, 0, 1, 3, 0, 0, 0, 0, 2, 0, 0, 4, 3, 2, 4, 3, 5, 4, 3, 3, 3, 4, 3, 3, 4, 3, 3, 0, 2, 1, 0, 3, 3),
+ (0, 2, 0, 4, 0, 3, 0, 2, 0, 2, 5, 5, 3, 4, 4, 4, 4, 1, 4, 3, 3, 0, 4, 3, 4, 3, 1, 3, 3, 2, 4, 3, 0, 3, 4, 3, 0, 3, 4, 4, 2, 4, 4, 0, 4, 5, 3, 3, 2, 2, 1, 1, 1, 2, 0, 1, 5, 0, 3, 3, 2, 4, 3, 3, 3, 4, 0, 3, 0, 2, 0, 4, 4, 3, 5, 5, 0, 0, 3, 0, 2, 3, 3),
+ (0, 3, 0, 4, 0, 3, 0, 1, 0, 3, 4, 3, 3, 1, 3, 3, 3, 0, 3, 1, 3, 0, 4, 3, 3, 1, 1, 0, 3, 0, 3, 3, 0, 0, 4, 4, 0, 1, 5, 4, 3, 3, 5, 0, 3, 3, 4, 3, 0, 2, 0, 1, 1, 1, 0, 1, 3, 0, 1, 2, 1, 3, 3, 2, 3, 3, 0, 3, 0, 1, 0, 1, 3, 3, 4, 4, 1, 0, 1, 2, 2, 1, 3),
+ (0, 1, 0, 4, 0, 4, 0, 3, 0, 1, 3, 3, 3, 2, 3, 1, 1, 0, 3, 0, 3, 3, 4, 3, 2, 4, 2, 0, 1, 0, 4, 3, 2, 0, 4, 3, 0, 5, 3, 3, 2, 4, 4, 4, 3, 3, 3, 4, 0, 1, 3, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 4, 2, 3, 3, 3, 0, 3, 0, 0, 0, 4, 4, 4, 5, 3, 2, 0, 3, 3, 0, 3, 5),
+ (0, 2, 0, 3, 0, 0, 0, 3, 0, 1, 3, 0, 2, 0, 0, 0, 1, 0, 3, 1, 1, 3, 3, 0, 0, 3, 0, 0, 3, 0, 2, 3, 1, 0, 3, 1, 0, 3, 3, 2, 0, 4, 2, 2, 0, 2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 2, 0, 1, 0, 1, 0, 0, 0, 1, 3, 1, 2, 0, 0, 0, 1, 0, 0, 1, 4),
+ (0, 3, 0, 3, 0, 5, 0, 1, 0, 2, 4, 3, 1, 3, 3, 2, 1, 1, 5, 2, 1, 0, 5, 1, 2, 0, 0, 0, 3, 3, 2, 2, 3, 2, 4, 3, 0, 0, 3, 3, 1, 3, 3, 0, 2, 5, 3, 4, 0, 3, 3, 0, 1, 2, 0, 2, 2, 0, 3, 2, 0, 2, 2, 3, 3, 3, 0, 2, 0, 1, 0, 3, 4, 4, 2, 5, 4, 0, 3, 0, 0, 3, 5),
+ (0, 3, 0, 3, 0, 3, 0, 1, 0, 3, 3, 3, 3, 0, 3, 0, 2, 0, 2, 1, 1, 0, 2, 0, 1, 0, 0, 0, 2, 1, 0, 0, 1, 0, 3, 2, 0, 0, 3, 3, 1, 2, 3, 1, 0, 3, 3, 0, 0, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 2, 3, 1, 2, 3, 0, 3, 0, 1, 0, 3, 2, 1, 0, 4, 3, 0, 1, 1, 0, 3, 3),
+ (0, 4, 0, 5, 0, 3, 0, 3, 0, 4, 5, 5, 4, 3, 5, 3, 4, 3, 5, 3, 3, 2, 5, 3, 4, 4, 4, 3, 4, 3, 4, 5, 5, 3, 4, 4, 3, 4, 4, 5, 4, 4, 4, 3, 4, 5, 5, 4, 2, 3, 4, 2, 3, 4, 0, 3, 3, 1, 4, 3, 2, 4, 3, 3, 5, 5, 0, 3, 0, 3, 0, 5, 5, 5, 5, 4, 4, 0, 4, 0, 1, 4, 4),
+ (0, 4, 0, 4, 0, 3, 0, 3, 0, 3, 5, 4, 4, 2, 3, 2, 5, 1, 3, 2, 5, 1, 4, 2, 3, 2, 3, 3, 4, 3, 3, 3, 3, 2, 5, 4, 1, 3, 3, 5, 3, 4, 4, 0, 4, 4, 3, 1, 1, 3, 1, 0, 2, 3, 0, 2, 3, 0, 3, 0, 0, 4, 3, 1, 3, 4, 0, 3, 0, 2, 0, 4, 4, 4, 3, 4, 5, 0, 4, 0, 0, 3, 4),
+ (0, 3, 0, 3, 0, 3, 1, 2, 0, 3, 4, 4, 3, 3, 3, 0, 2, 2, 4, 3, 3, 1, 3, 3, 3, 1, 1, 0, 3, 1, 4, 3, 2, 3, 4, 4, 2, 4, 4, 4, 3, 4, 4, 3, 2, 4, 4, 3, 1, 3, 3, 1, 3, 3, 0, 4, 1, 0, 2, 2, 1, 4, 3, 2, 3, 3, 5, 4, 3, 3, 5, 4, 4, 3, 3, 0, 4, 0, 3, 2, 2, 4, 4),
+ (0, 2, 0, 1, 0, 0, 0, 0, 0, 1, 2, 1, 3, 0, 0, 0, 0, 0, 2, 0, 1, 2, 1, 0, 0, 1, 0, 0, 0, 0, 3, 0, 0, 1, 0, 1, 1, 3, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 0, 3, 4, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1),
+ (0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 4, 0, 4, 1, 4, 0, 3, 0, 4, 0, 3, 0, 4, 0, 3, 0, 3, 0, 4, 1, 5, 1, 4, 0, 0, 3, 0, 5, 0, 5, 2, 0, 1, 0, 0, 0, 2, 1, 4, 0, 1, 3, 0, 0, 3, 0, 0, 3, 1, 1, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0),
+ (1, 4, 0, 5, 0, 3, 0, 2, 0, 3, 5, 4, 4, 3, 4, 3, 5, 3, 4, 3, 3, 0, 4, 3, 3, 3, 3, 3, 3, 2, 4, 4, 3, 1, 3, 4, 4, 5, 4, 4, 3, 4, 4, 1, 3, 5, 4, 3, 3, 3, 1, 2, 2, 3, 3, 1, 3, 1, 3, 3, 3, 5, 3, 3, 4, 5, 0, 3, 0, 3, 0, 3, 4, 3, 4, 4, 3, 0, 3, 0, 2, 4, 3),
+ (0, 1, 0, 4, 0, 0, 0, 0, 0, 1, 4, 0, 4, 1, 4, 2, 4, 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 2, 0, 3, 1, 1, 1, 0, 3, 0, 0, 0, 1, 2, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 3, 0, 0, 0, 0, 3, 2, 0, 2, 2, 0, 1, 0, 0, 0, 2, 3, 2, 3, 3, 0, 0, 0, 0, 2, 1, 0),
+ (0, 5, 1, 5, 0, 3, 0, 3, 0, 5, 4, 4, 5, 1, 5, 3, 3, 0, 4, 3, 4, 3, 5, 3, 4, 3, 3, 2, 4, 3, 4, 3, 3, 0, 3, 3, 1, 4, 4, 3, 4, 4, 4, 3, 4, 5, 5, 3, 2, 3, 1, 1, 3, 3, 1, 3, 1, 1, 3, 3, 2, 4, 5, 3, 3, 5, 0, 4, 0, 3, 0, 4, 4, 3, 5, 3, 3, 0, 3, 4, 0, 4, 3),
+ (0, 5, 0, 5, 0, 3, 0, 2, 0, 4, 4, 3, 5, 2, 4, 3, 3, 3, 4, 4, 4, 3, 5, 3, 5, 3, 3, 1, 4, 0, 4, 3, 3, 0, 3, 3, 0, 4, 4, 4, 4, 5, 4, 3, 3, 5, 5, 3, 2, 3, 1, 2, 3, 2, 0, 1, 0, 0, 3, 2, 2, 4, 4, 3, 1, 5, 0, 4, 0, 3, 0, 4, 3, 1, 3, 2, 1, 0, 3, 3, 0, 3, 3),
+ (0, 4, 0, 5, 0, 5, 0, 4, 0, 4, 5, 5, 5, 3, 4, 3, 3, 2, 5, 4, 4, 3, 5, 3, 5, 3, 4, 0, 4, 3, 4, 4, 3, 2, 4, 4, 3, 4, 5, 4, 4, 5, 5, 0, 3, 5, 5, 4, 1, 3, 3, 2, 3, 3, 1, 3, 1, 0, 4, 3, 1, 4, 4, 3, 4, 5, 0, 4, 0, 2, 0, 4, 3, 4, 4, 3, 3, 0, 4, 0, 0, 5, 5),
+ (0, 4, 0, 4, 0, 5, 0, 1, 1, 3, 3, 4, 4, 3, 4, 1, 3, 0, 5, 1, 3, 0, 3, 1, 3, 1, 1, 0, 3, 0, 3, 3, 4, 0, 4, 3, 0, 4, 4, 4, 3, 4, 4, 0, 3, 5, 4, 1, 0, 3, 0, 0, 2, 3, 0, 3, 1, 0, 3, 1, 0, 3, 2, 1, 3, 5, 0, 3, 0, 1, 0, 3, 2, 3, 3, 4, 4, 0, 2, 2, 0, 4, 4),
+ (2, 4, 0, 5, 0, 4, 0, 3, 0, 4, 5, 5, 4, 3, 5, 3, 5, 3, 5, 3, 5, 2, 5, 3, 4, 3, 3, 4, 3, 4, 5, 3, 2, 1, 5, 4, 3, 2, 3, 4, 5, 3, 4, 1, 2, 5, 4, 3, 0, 3, 3, 0, 3, 2, 0, 2, 3, 0, 4, 1, 0, 3, 4, 3, 3, 5, 0, 3, 0, 1, 0, 4, 5, 5, 5, 4, 3, 0, 4, 2, 0, 3, 5),
+ (0, 5, 0, 4, 0, 4, 0, 2, 0, 5, 4, 3, 4, 3, 4, 3, 3, 3, 4, 3, 4, 2, 5, 3, 5, 3, 4, 1, 4, 3, 4, 4, 4, 0, 3, 5, 0, 4, 4, 4, 4, 5, 3, 1, 3, 4, 5, 3, 3, 3, 3, 3, 3, 3, 0, 2, 2, 0, 3, 3, 2, 4, 3, 3, 3, 5, 3, 4, 1, 3, 3, 5, 3, 2, 0, 0, 0, 0, 4, 3, 1, 3, 3),
+ (0, 1, 0, 3, 0, 3, 0, 1, 0, 1, 3, 3, 3, 2, 3, 3, 3, 0, 3, 0, 0, 0, 3, 1, 3, 0, 0, 0, 2, 2, 2, 3, 0, 0, 3, 2, 0, 1, 2, 4, 1, 3, 3, 0, 0, 3, 3, 3, 0, 1, 0, 0, 2, 1, 0, 0, 3, 0, 3, 1, 0, 3, 0, 0, 1, 3, 0, 2, 0, 1, 0, 3, 3, 1, 3, 3, 0, 0, 1, 1, 0, 3, 3),
+ (0, 2, 0, 3, 0, 2, 1, 4, 0, 2, 2, 3, 1, 1, 3, 1, 1, 0, 2, 0, 3, 1, 2, 3, 1, 3, 0, 0, 1, 0, 4, 3, 2, 3, 3, 3, 1, 4, 2, 3, 3, 3, 3, 1, 0, 3, 1, 4, 0, 1, 1, 0, 1, 2, 0, 1, 1, 0, 1, 1, 0, 3, 1, 3, 2, 2, 0, 1, 0, 0, 0, 2, 3, 3, 3, 1, 0, 0, 0, 0, 0, 2, 3),
+ (0, 5, 0, 4, 0, 5, 0, 2, 0, 4, 5, 5, 3, 3, 4, 3, 3, 1, 5, 4, 4, 2, 4, 4, 4, 3, 4, 2, 4, 3, 5, 5, 4, 3, 3, 4, 3, 3, 5, 5, 4, 5, 5, 1, 3, 4, 5, 3, 1, 4, 3, 1, 3, 3, 0, 3, 3, 1, 4, 3, 1, 4, 5, 3, 3, 5, 0, 4, 0, 3, 0, 5, 3, 3, 1, 4, 3, 0, 4, 0, 1, 5, 3),
+ (0, 5, 0, 5, 0, 4, 0, 2, 0, 4, 4, 3, 4, 3, 3, 3, 3, 3, 5, 4, 4, 4, 4, 4, 4, 5, 3, 3, 5, 2, 4, 4, 4, 3, 4, 4, 3, 3, 4, 4, 5, 5, 3, 3, 4, 3, 4, 3, 3, 4, 3, 3, 3, 3, 1, 2, 2, 1, 4, 3, 3, 5, 4, 4, 3, 4, 0, 4, 0, 3, 0, 4, 4, 4, 4, 4, 1, 0, 4, 2, 0, 2, 4),
+ (0, 4, 0, 4, 0, 3, 0, 1, 0, 3, 5, 2, 3, 0, 3, 0, 2, 1, 4, 2, 3, 3, 4, 1, 4, 3, 3, 2, 4, 1, 3, 3, 3, 0, 3, 3, 0, 0, 3, 3, 3, 5, 3, 3, 3, 3, 3, 2, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 1, 0, 0, 3, 1, 2, 2, 3, 0, 3, 0, 2, 0, 4, 4, 3, 3, 4, 1, 0, 3, 0, 0, 2, 4),
+ (0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 2, 0, 1, 0, 0, 0, 0, 0, 3, 1, 3, 0, 3, 2, 0, 0, 0, 1, 0, 3, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 0, 2, 0, 0, 0, 0, 0, 0, 2),
+ (0, 2, 1, 3, 0, 2, 0, 2, 0, 3, 3, 3, 3, 1, 3, 1, 3, 3, 3, 3, 3, 3, 4, 2, 2, 1, 2, 1, 4, 0, 4, 3, 1, 3, 3, 3, 2, 4, 3, 5, 4, 3, 3, 3, 3, 3, 3, 3, 0, 1, 3, 0, 2, 0, 0, 1, 0, 0, 1, 0, 0, 4, 2, 0, 2, 3, 0, 3, 3, 0, 3, 3, 4, 2, 3, 1, 4, 0, 1, 2, 0, 2, 3),
+ (0, 3, 0, 3, 0, 1, 0, 3, 0, 2, 3, 3, 3, 0, 3, 1, 2, 0, 3, 3, 2, 3, 3, 2, 3, 2, 3, 1, 3, 0, 4, 3, 2, 0, 3, 3, 1, 4, 3, 3, 2, 3, 4, 3, 1, 3, 3, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 4, 1, 1, 0, 3, 0, 3, 1, 0, 2, 3, 3, 3, 3, 3, 1, 0, 0, 2, 0, 3, 3),
+ (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 3, 0, 3, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 2, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3),
+ (0, 2, 0, 3, 1, 3, 0, 3, 0, 2, 3, 3, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 1, 3, 0, 2, 3, 1, 1, 4, 3, 3, 2, 3, 3, 1, 2, 2, 4, 1, 3, 3, 0, 1, 4, 2, 3, 0, 1, 3, 0, 3, 0, 0, 1, 3, 0, 2, 0, 0, 3, 3, 2, 1, 3, 0, 3, 0, 2, 0, 3, 4, 4, 4, 3, 1, 0, 3, 0, 0, 3, 3),
+ (0, 2, 0, 1, 0, 2, 0, 0, 0, 1, 3, 2, 2, 1, 3, 0, 1, 1, 3, 0, 3, 2, 3, 1, 2, 0, 2, 0, 1, 1, 3, 3, 3, 0, 3, 3, 1, 1, 2, 3, 2, 3, 3, 1, 2, 3, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 2, 1, 2, 1, 3, 0, 3, 0, 0, 0, 3, 4, 4, 4, 3, 2, 0, 2, 0, 0, 2, 4),
+ (0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 3, 1, 0, 0, 0, 0, 0, 0, 0, 3),
+ (0, 3, 0, 3, 0, 2, 0, 3, 0, 3, 3, 3, 2, 3, 2, 2, 2, 0, 3, 1, 3, 3, 3, 2, 3, 3, 0, 0, 3, 0, 3, 2, 2, 0, 2, 3, 1, 4, 3, 4, 3, 3, 2, 3, 1, 5, 4, 4, 0, 3, 1, 2, 1, 3, 0, 3, 1, 1, 2, 0, 2, 3, 1, 3, 1, 3, 0, 3, 0, 1, 0, 3, 3, 4, 4, 2, 1, 0, 2, 1, 0, 2, 4),
+ (0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 4, 2, 5, 1, 4, 0, 2, 0, 2, 1, 3, 1, 4, 0, 2, 1, 0, 0, 2, 1, 4, 1, 1, 0, 3, 3, 0, 5, 1, 3, 2, 3, 3, 1, 0, 3, 2, 3, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 4, 0, 1, 0, 3, 0, 2, 0, 1, 0, 3, 3, 3, 4, 3, 3, 0, 0, 0, 0, 2, 3),
+ (0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 1, 0, 0, 0, 0, 0, 3),
+ (0, 1, 0, 3, 0, 4, 0, 3, 0, 2, 4, 3, 1, 0, 3, 2, 2, 1, 3, 1, 2, 2, 3, 1, 1, 1, 2, 1, 3, 0, 1, 2, 0, 1, 3, 2, 1, 3, 0, 5, 5, 1, 0, 0, 1, 3, 2, 1, 0, 3, 0, 0, 1, 0, 0, 0, 0, 0, 3, 4, 0, 1, 1, 1, 3, 2, 0, 2, 0, 1, 0, 2, 3, 3, 1, 2, 3, 0, 1, 0, 1, 0, 4),
+ (0, 0, 0, 1, 0, 3, 0, 3, 0, 2, 2, 1, 0, 0, 4, 0, 3, 0, 3, 1, 3, 0, 3, 0, 3, 0, 1, 0, 3, 0, 3, 1, 3, 0, 3, 3, 0, 0, 1, 2, 1, 1, 1, 0, 1, 2, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 1, 2, 0, 0, 2, 0, 0, 0, 0, 2, 3, 3, 3, 3, 0, 0, 0, 0, 1, 4),
+ (0, 0, 0, 3, 0, 3, 0, 0, 0, 0, 3, 1, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 3, 0, 2, 0, 2, 3, 0, 0, 2, 2, 3, 1, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 2, 0, 0, 0, 0, 2, 3),
+ (2, 4, 0, 5, 0, 5, 0, 4, 0, 3, 4, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 4, 5, 4, 5, 5, 5, 2, 3, 0, 5, 5, 4, 1, 5, 4, 3, 1, 5, 4, 3, 4, 4, 3, 3, 4, 3, 3, 0, 3, 2, 0, 2, 3, 0, 3, 0, 0, 3, 3, 0, 5, 3, 2, 3, 3, 0, 3, 0, 3, 0, 3, 4, 5, 4, 5, 3, 0, 4, 3, 0, 3, 4),
+ (0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 3, 4, 3, 2, 3, 2, 3, 0, 4, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 2, 4, 3, 3, 1, 3, 4, 3, 4, 4, 4, 3, 4, 4, 3, 2, 4, 4, 1, 0, 2, 0, 0, 1, 1, 0, 2, 0, 0, 3, 1, 0, 5, 3, 2, 1, 3, 0, 3, 0, 1, 2, 4, 3, 2, 4, 3, 3, 0, 3, 2, 0, 4, 4),
+ (0, 3, 0, 3, 0, 1, 0, 0, 0, 1, 4, 3, 3, 2, 3, 1, 3, 1, 4, 2, 3, 2, 4, 2, 3, 4, 3, 0, 2, 2, 3, 3, 3, 0, 3, 3, 3, 0, 3, 4, 1, 3, 3, 0, 3, 4, 3, 3, 0, 1, 1, 0, 1, 0, 0, 0, 4, 0, 3, 0, 0, 3, 1, 2, 1, 3, 0, 4, 0, 1, 0, 4, 3, 3, 4, 3, 3, 0, 2, 0, 0, 3, 3),
+ (0, 3, 0, 4, 0, 1, 0, 3, 0, 3, 4, 3, 3, 0, 3, 3, 3, 1, 3, 1, 3, 3, 4, 3, 3, 3, 0, 0, 3, 1, 5, 3, 3, 1, 3, 3, 2, 5, 4, 3, 3, 4, 5, 3, 2, 5, 3, 4, 0, 1, 0, 0, 0, 0, 0, 2, 0, 0, 1, 1, 0, 4, 2, 2, 1, 3, 0, 3, 0, 2, 0, 4, 4, 3, 5, 3, 2, 0, 1, 1, 0, 3, 4),
+ (0, 5, 0, 4, 0, 5, 0, 2, 0, 4, 4, 3, 3, 2, 3, 3, 3, 1, 4, 3, 4, 1, 5, 3, 4, 3, 4, 0, 4, 2, 4, 3, 4, 1, 5, 4, 0, 4, 4, 4, 4, 5, 4, 1, 3, 5, 4, 2, 1, 4, 1, 1, 3, 2, 0, 3, 1, 0, 3, 2, 1, 4, 3, 3, 3, 4, 0, 4, 0, 3, 0, 4, 4, 4, 3, 3, 3, 0, 4, 2, 0, 3, 4),
+ (1, 4, 0, 4, 0, 3, 0, 1, 0, 3, 3, 3, 1, 1, 3, 3, 2, 2, 3, 3, 1, 0, 3, 2, 2, 1, 2, 0, 3, 1, 2, 1, 2, 0, 3, 2, 0, 2, 2, 3, 3, 4, 3, 0, 3, 3, 1, 2, 0, 1, 1, 3, 1, 2, 0, 0, 3, 0, 1, 1, 0, 3, 2, 2, 3, 3, 0, 3, 0, 0, 0, 2, 3, 3, 4, 3, 3, 0, 1, 0, 0, 1, 4),
+ (0, 4, 0, 4, 0, 4, 0, 0, 0, 3, 4, 4, 3, 1, 4, 2, 3, 2, 3, 3, 3, 1, 4, 3, 4, 0, 3, 0, 4, 2, 3, 3, 2, 2, 5, 4, 2, 1, 3, 4, 3, 4, 3, 1, 3, 3, 4, 2, 0, 2, 1, 0, 3, 3, 0, 0, 2, 0, 3, 1, 0, 4, 4, 3, 4, 3, 0, 4, 0, 1, 0, 2, 4, 4, 4, 4, 4, 0, 3, 2, 0, 3, 3),
+ (0, 0, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 3, 2, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2),
+ (0, 2, 0, 3, 0, 4, 0, 4, 0, 1, 3, 3, 3, 0, 4, 0, 2, 1, 2, 1, 1, 1, 2, 0, 3, 1, 1, 0, 1, 0, 3, 1, 0, 0, 3, 3, 2, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 2, 0, 2, 2, 0, 3, 1, 0, 0, 1, 0, 1, 1, 0, 1, 2, 0, 3, 0, 0, 0, 0, 1, 0, 0, 3, 3, 4, 3, 1, 0, 1, 0, 3, 0, 2),
+ (0, 0, 0, 3, 0, 5, 0, 0, 0, 0, 1, 0, 2, 0, 3, 1, 0, 1, 3, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 4, 0, 0, 0, 2, 3, 0, 1, 4, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 3, 0, 0, 0, 0, 0, 3),
+ (0, 2, 0, 5, 0, 5, 0, 1, 0, 2, 4, 3, 3, 2, 5, 1, 3, 2, 3, 3, 3, 0, 4, 1, 2, 0, 3, 0, 4, 0, 2, 2, 1, 1, 5, 3, 0, 0, 1, 4, 2, 3, 2, 0, 3, 3, 3, 2, 0, 2, 4, 1, 1, 2, 0, 1, 1, 0, 3, 1, 0, 1, 3, 1, 2, 3, 0, 2, 0, 0, 0, 1, 3, 5, 4, 4, 4, 0, 3, 0, 0, 1, 3),
+ (0, 4, 0, 5, 0, 4, 0, 4, 0, 4, 5, 4, 3, 3, 4, 3, 3, 3, 4, 3, 4, 4, 5, 3, 4, 5, 4, 2, 4, 2, 3, 4, 3, 1, 4, 4, 1, 3, 5, 4, 4, 5, 5, 4, 4, 5, 5, 5, 2, 3, 3, 1, 4, 3, 1, 3, 3, 0, 3, 3, 1, 4, 3, 4, 4, 4, 0, 3, 0, 4, 0, 3, 3, 4, 4, 5, 0, 0, 4, 3, 0, 4, 5),
+ (0, 4, 0, 4, 0, 3, 0, 3, 0, 3, 4, 4, 4, 3, 3, 2, 4, 3, 4, 3, 4, 3, 5, 3, 4, 3, 2, 1, 4, 2, 4, 4, 3, 1, 3, 4, 2, 4, 5, 5, 3, 4, 5, 4, 1, 5, 4, 3, 0, 3, 2, 2, 3, 2, 1, 3, 1, 0, 3, 3, 3, 5, 3, 3, 3, 5, 4, 4, 2, 3, 3, 4, 3, 3, 3, 2, 1, 0, 3, 2, 1, 4, 3),
+ (0, 4, 0, 5, 0, 4, 0, 3, 0, 3, 5, 5, 3, 2, 4, 3, 4, 0, 5, 4, 4, 1, 4, 4, 4, 3, 3, 3, 4, 3, 5, 5, 2, 3, 3, 4, 1, 2, 5, 5, 3, 5, 5, 2, 3, 5, 5, 4, 0, 3, 2, 0, 3, 3, 1, 1, 5, 1, 4, 1, 0, 4, 3, 2, 3, 5, 0, 4, 0, 3, 0, 5, 4, 3, 4, 3, 0, 0, 4, 1, 0, 4, 4),
+ (1, 3, 0, 4, 0, 2, 0, 2, 0, 2, 5, 5, 3, 3, 3, 3, 3, 0, 4, 2, 3, 4, 4, 4, 3, 4, 0, 0, 3, 4, 5, 4, 3, 3, 3, 3, 2, 5, 5, 4, 5, 5, 5, 4, 3, 5, 5, 5, 1, 3, 1, 0, 1, 0, 0, 3, 2, 0, 4, 2, 0, 5, 2, 3, 2, 4, 1, 3, 0, 3, 0, 4, 5, 4, 5, 4, 3, 0, 4, 2, 0, 5, 4),
+ (0, 3, 0, 4, 0, 5, 0, 3, 0, 3, 4, 4, 3, 2, 3, 2, 3, 3, 3, 3, 3, 2, 4, 3, 3, 2, 2, 0, 3, 3, 3, 3, 3, 1, 3, 3, 3, 0, 4, 4, 3, 4, 4, 1, 1, 4, 4, 2, 0, 3, 1, 0, 1, 1, 0, 4, 1, 0, 2, 3, 1, 3, 3, 1, 3, 4, 0, 3, 0, 1, 0, 3, 1, 3, 0, 0, 1, 0, 2, 0, 0, 4, 4),
+ (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ (0, 3, 0, 3, 0, 2, 0, 3, 0, 1, 5, 4, 3, 3, 3, 1, 4, 2, 1, 2, 3, 4, 4, 2, 4, 4, 5, 0, 3, 1, 4, 3, 4, 0, 4, 3, 3, 3, 2, 3, 2, 5, 3, 4, 3, 2, 2, 3, 0, 0, 3, 0, 2, 1, 0, 1, 2, 0, 0, 0, 0, 2, 1, 1, 3, 1, 0, 2, 0, 4, 0, 3, 4, 4, 4, 5, 2, 0, 2, 0, 0, 1, 3),
+ (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 4, 2, 1, 1, 0, 1, 0, 3, 2, 0, 0, 3, 1, 1, 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 4, 0, 4, 2, 1, 0, 0, 0, 0, 0, 1),
+ (0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 2, 0, 2, 1, 0, 0, 1, 2, 1, 0, 1, 1, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 1, 0, 0, 0, 0, 0, 1, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2),
+ (0, 4, 0, 4, 0, 4, 0, 3, 0, 4, 4, 3, 4, 2, 4, 3, 2, 0, 4, 4, 4, 3, 5, 3, 5, 3, 3, 2, 4, 2, 4, 3, 4, 3, 1, 4, 0, 2, 3, 4, 4, 4, 3, 3, 3, 4, 4, 4, 3, 4, 1, 3, 4, 3, 2, 1, 2, 1, 3, 3, 3, 4, 4, 3, 3, 5, 0, 4, 0, 3, 0, 4, 3, 3, 3, 2, 1, 0, 3, 0, 0, 3, 3),
+ (0, 4, 0, 3, 0, 3, 0, 3, 0, 3, 5, 5, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 4, 4, 3, 3, 3, 3, 4, 3, 5, 3, 3, 1, 3, 2, 4, 5, 5, 5, 5, 4, 3, 4, 5, 5, 3, 2, 2, 3, 3, 3, 3, 2, 3, 3, 1, 2, 3, 2, 4, 3, 3, 3, 4, 0, 4, 0, 2, 0, 4, 3, 2, 2, 1, 2, 0, 3, 0, 0, 4, 1),
)
+# fmt: on
-class JapaneseContextAnalysis(object):
+
+class JapaneseContextAnalysis:
NUM_OF_CATEGORY = 6
DONT_KNOW = -1
ENOUGH_REL_THRESHOLD = 100
MAX_REL_THRESHOLD = 1000
MINIMUM_DATA_THRESHOLD = 4
- def __init__(self):
- self._total_rel = None
- self._rel_sample = None
- self._need_to_skip_char_num = None
- self._last_char_order = None
- self._done = None
+ def __init__(self) -> None:
+ self._total_rel = 0
+ self._rel_sample: List[int] = []
+ self._need_to_skip_char_num = 0
+ self._last_char_order = -1
+ self._done = False
self.reset()
- def reset(self):
+ def reset(self) -> None:
self._total_rel = 0 # total sequence received
# category counters, each integer counts sequence in its category
self._rel_sample = [0] * self.NUM_OF_CATEGORY
@@ -140,7 +144,7 @@ class JapaneseContextAnalysis(object):
# been made
self._done = False
- def feed(self, byte_str, num_bytes):
+ def feed(self, byte_str: Union[bytes, bytearray], num_bytes: int) -> None:
if self._done:
return
@@ -153,7 +157,7 @@ class JapaneseContextAnalysis(object):
# this character will simply our logic and improve performance.
i = self._need_to_skip_char_num
while i < num_bytes:
- order, char_len = self.get_order(byte_str[i:i + 2])
+ order, char_len = self.get_order(byte_str[i : i + 2])
i += char_len
if i > num_bytes:
self._need_to_skip_char_num = i - num_bytes
@@ -164,32 +168,34 @@ class JapaneseContextAnalysis(object):
if self._total_rel > self.MAX_REL_THRESHOLD:
self._done = True
break
- self._rel_sample[jp2CharContext[self._last_char_order][order]] += 1
+ self._rel_sample[
+ jp2_char_context[self._last_char_order][order]
+ ] += 1
self._last_char_order = order
- def got_enough_data(self):
+ def got_enough_data(self) -> bool:
return self._total_rel > self.ENOUGH_REL_THRESHOLD
- def get_confidence(self):
+ def get_confidence(self) -> float:
# This is just one way to calculate confidence. It works well for me.
if self._total_rel > self.MINIMUM_DATA_THRESHOLD:
return (self._total_rel - self._rel_sample[0]) / self._total_rel
- else:
- return self.DONT_KNOW
+ return self.DONT_KNOW
- def get_order(self, byte_str):
+ def get_order(self, _: Union[bytes, bytearray]) -> Tuple[int, int]:
return -1, 1
+
class SJISContextAnalysis(JapaneseContextAnalysis):
- def __init__(self):
- super(SJISContextAnalysis, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
self._charset_name = "SHIFT_JIS"
@property
- def charset_name(self):
+ def charset_name(self) -> str:
return self._charset_name
- def get_order(self, byte_str):
+ def get_order(self, byte_str: Union[bytes, bytearray]) -> Tuple[int, int]:
if not byte_str:
return -1, 1
# find out current char's byte length
@@ -209,8 +215,9 @@ class SJISContextAnalysis(JapaneseContextAnalysis):
return -1, char_len
+
class EUCJPContextAnalysis(JapaneseContextAnalysis):
- def get_order(self, byte_str):
+ def get_order(self, byte_str: Union[bytes, bytearray]) -> Tuple[int, int]:
if not byte_str:
return -1, 1
# find out current char's byte length
@@ -229,5 +236,3 @@ class EUCJPContextAnalysis(JapaneseContextAnalysis):
return second_char - 0xA1, char_len
return -1, char_len
-
-
diff --git a/src/pip/_vendor/chardet/langbulgarianmodel.py b/src/pip/_vendor/chardet/langbulgarianmodel.py
index e963a5097..994668219 100644
--- a/src/pip/_vendor/chardet/langbulgarianmodel.py
+++ b/src/pip/_vendor/chardet/langbulgarianmodel.py
@@ -1,9 +1,5 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
from pip._vendor.chardet.sbcharsetprober import SingleByteCharSetModel
-
# 3: Positive
# 2: Likely
# 1: Unlikely
@@ -4115,536 +4111,539 @@ BULGARIAN_LANG_MODEL = {
# Character Mapping Table(s):
ISO_8859_5_BULGARIAN_CHAR_TO_ORDER = {
- 0: 255, # '\x00'
- 1: 255, # '\x01'
- 2: 255, # '\x02'
- 3: 255, # '\x03'
- 4: 255, # '\x04'
- 5: 255, # '\x05'
- 6: 255, # '\x06'
- 7: 255, # '\x07'
- 8: 255, # '\x08'
- 9: 255, # '\t'
- 10: 254, # '\n'
- 11: 255, # '\x0b'
- 12: 255, # '\x0c'
- 13: 254, # '\r'
- 14: 255, # '\x0e'
- 15: 255, # '\x0f'
- 16: 255, # '\x10'
- 17: 255, # '\x11'
- 18: 255, # '\x12'
- 19: 255, # '\x13'
- 20: 255, # '\x14'
- 21: 255, # '\x15'
- 22: 255, # '\x16'
- 23: 255, # '\x17'
- 24: 255, # '\x18'
- 25: 255, # '\x19'
- 26: 255, # '\x1a'
- 27: 255, # '\x1b'
- 28: 255, # '\x1c'
- 29: 255, # '\x1d'
- 30: 255, # '\x1e'
- 31: 255, # '\x1f'
- 32: 253, # ' '
- 33: 253, # '!'
- 34: 253, # '"'
- 35: 253, # '#'
- 36: 253, # '$'
- 37: 253, # '%'
- 38: 253, # '&'
- 39: 253, # "'"
- 40: 253, # '('
- 41: 253, # ')'
- 42: 253, # '*'
- 43: 253, # '+'
- 44: 253, # ','
- 45: 253, # '-'
- 46: 253, # '.'
- 47: 253, # '/'
- 48: 252, # '0'
- 49: 252, # '1'
- 50: 252, # '2'
- 51: 252, # '3'
- 52: 252, # '4'
- 53: 252, # '5'
- 54: 252, # '6'
- 55: 252, # '7'
- 56: 252, # '8'
- 57: 252, # '9'
- 58: 253, # ':'
- 59: 253, # ';'
- 60: 253, # '<'
- 61: 253, # '='
- 62: 253, # '>'
- 63: 253, # '?'
- 64: 253, # '@'
- 65: 77, # 'A'
- 66: 90, # 'B'
- 67: 99, # 'C'
- 68: 100, # 'D'
- 69: 72, # 'E'
- 70: 109, # 'F'
- 71: 107, # 'G'
- 72: 101, # 'H'
- 73: 79, # 'I'
- 74: 185, # 'J'
- 75: 81, # 'K'
- 76: 102, # 'L'
- 77: 76, # 'M'
- 78: 94, # 'N'
- 79: 82, # 'O'
- 80: 110, # 'P'
- 81: 186, # 'Q'
- 82: 108, # 'R'
- 83: 91, # 'S'
- 84: 74, # 'T'
- 85: 119, # 'U'
- 86: 84, # 'V'
- 87: 96, # 'W'
- 88: 111, # 'X'
- 89: 187, # 'Y'
- 90: 115, # 'Z'
- 91: 253, # '['
- 92: 253, # '\\'
- 93: 253, # ']'
- 94: 253, # '^'
- 95: 253, # '_'
- 96: 253, # '`'
- 97: 65, # 'a'
- 98: 69, # 'b'
- 99: 70, # 'c'
- 100: 66, # 'd'
- 101: 63, # 'e'
- 102: 68, # 'f'
- 103: 112, # 'g'
- 104: 103, # 'h'
- 105: 92, # 'i'
- 106: 194, # 'j'
- 107: 104, # 'k'
- 108: 95, # 'l'
- 109: 86, # 'm'
- 110: 87, # 'n'
- 111: 71, # 'o'
- 112: 116, # 'p'
- 113: 195, # 'q'
- 114: 85, # 'r'
- 115: 93, # 's'
- 116: 97, # 't'
- 117: 113, # 'u'
- 118: 196, # 'v'
- 119: 197, # 'w'
- 120: 198, # 'x'
- 121: 199, # 'y'
- 122: 200, # 'z'
- 123: 253, # '{'
- 124: 253, # '|'
- 125: 253, # '}'
- 126: 253, # '~'
- 127: 253, # '\x7f'
- 128: 194, # '\x80'
- 129: 195, # '\x81'
- 130: 196, # '\x82'
- 131: 197, # '\x83'
- 132: 198, # '\x84'
- 133: 199, # '\x85'
- 134: 200, # '\x86'
- 135: 201, # '\x87'
- 136: 202, # '\x88'
- 137: 203, # '\x89'
- 138: 204, # '\x8a'
- 139: 205, # '\x8b'
- 140: 206, # '\x8c'
- 141: 207, # '\x8d'
- 142: 208, # '\x8e'
- 143: 209, # '\x8f'
- 144: 210, # '\x90'
- 145: 211, # '\x91'
- 146: 212, # '\x92'
- 147: 213, # '\x93'
- 148: 214, # '\x94'
- 149: 215, # '\x95'
- 150: 216, # '\x96'
- 151: 217, # '\x97'
- 152: 218, # '\x98'
- 153: 219, # '\x99'
- 154: 220, # '\x9a'
- 155: 221, # '\x9b'
- 156: 222, # '\x9c'
- 157: 223, # '\x9d'
- 158: 224, # '\x9e'
- 159: 225, # '\x9f'
- 160: 81, # '\xa0'
- 161: 226, # 'Ё'
- 162: 227, # 'Ђ'
- 163: 228, # 'Ѓ'
- 164: 229, # 'Є'
- 165: 230, # 'Ѕ'
- 166: 105, # 'І'
- 167: 231, # 'Ї'
- 168: 232, # 'Ј'
- 169: 233, # 'Љ'
- 170: 234, # 'Њ'
- 171: 235, # 'Ћ'
- 172: 236, # 'Ќ'
- 173: 45, # '\xad'
- 174: 237, # 'Ў'
- 175: 238, # 'Џ'
- 176: 31, # 'А'
- 177: 32, # 'Б'
- 178: 35, # 'В'
- 179: 43, # 'Г'
- 180: 37, # 'Д'
- 181: 44, # 'Е'
- 182: 55, # 'Ж'
- 183: 47, # 'З'
- 184: 40, # 'И'
- 185: 59, # 'Й'
- 186: 33, # 'К'
- 187: 46, # 'Л'
- 188: 38, # 'М'
- 189: 36, # 'Н'
- 190: 41, # 'О'
- 191: 30, # 'П'
- 192: 39, # 'Р'
- 193: 28, # 'С'
- 194: 34, # 'Т'
- 195: 51, # 'У'
- 196: 48, # 'Ф'
- 197: 49, # 'Х'
- 198: 53, # 'Ц'
- 199: 50, # 'Ч'
- 200: 54, # 'Ш'
- 201: 57, # 'Щ'
- 202: 61, # 'Ъ'
- 203: 239, # 'Ы'
- 204: 67, # 'Ь'
- 205: 240, # 'Э'
- 206: 60, # 'Ю'
- 207: 56, # 'Я'
- 208: 1, # 'а'
- 209: 18, # 'б'
- 210: 9, # 'в'
- 211: 20, # 'г'
- 212: 11, # 'д'
- 213: 3, # 'е'
- 214: 23, # 'ж'
- 215: 15, # 'з'
- 216: 2, # 'и'
- 217: 26, # 'й'
- 218: 12, # 'к'
- 219: 10, # 'л'
- 220: 14, # 'м'
- 221: 6, # 'н'
- 222: 4, # 'о'
- 223: 13, # 'п'
- 224: 7, # 'р'
- 225: 8, # 'с'
- 226: 5, # 'т'
- 227: 19, # 'у'
- 228: 29, # 'ф'
- 229: 25, # 'х'
- 230: 22, # 'ц'
- 231: 21, # 'ч'
- 232: 27, # 'ш'
- 233: 24, # 'щ'
- 234: 17, # 'ъ'
- 235: 75, # 'ы'
- 236: 52, # 'ь'
- 237: 241, # 'э'
- 238: 42, # 'ю'
- 239: 16, # 'я'
- 240: 62, # '№'
- 241: 242, # 'ё'
- 242: 243, # 'ђ'
- 243: 244, # 'ѓ'
- 244: 58, # 'є'
- 245: 245, # 'ѕ'
- 246: 98, # 'і'
- 247: 246, # 'ї'
- 248: 247, # 'ј'
- 249: 248, # 'љ'
- 250: 249, # 'њ'
- 251: 250, # 'ћ'
- 252: 251, # 'ќ'
- 253: 91, # '§'
- 254: 252, # 'ў'
- 255: 253, # 'џ'
+ 0: 255, # '\x00'
+ 1: 255, # '\x01'
+ 2: 255, # '\x02'
+ 3: 255, # '\x03'
+ 4: 255, # '\x04'
+ 5: 255, # '\x05'
+ 6: 255, # '\x06'
+ 7: 255, # '\x07'
+ 8: 255, # '\x08'
+ 9: 255, # '\t'
+ 10: 254, # '\n'
+ 11: 255, # '\x0b'
+ 12: 255, # '\x0c'
+ 13: 254, # '\r'
+ 14: 255, # '\x0e'
+ 15: 255, # '\x0f'
+ 16: 255, # '\x10'
+ 17: 255, # '\x11'
+ 18: 255, # '\x12'
+ 19: 255, # '\x13'
+ 20: 255, # '\x14'
+ 21: 255, # '\x15'
+ 22: 255, # '\x16'
+ 23: 255, # '\x17'
+ 24: 255, # '\x18'
+ 25: 255, # '\x19'
+ 26: 255, # '\x1a'
+ 27: 255, # '\x1b'
+ 28: 255, # '\x1c'
+ 29: 255, # '\x1d'
+ 30: 255, # '\x1e'
+ 31: 255, # '\x1f'
+ 32: 253, # ' '
+ 33: 253, # '!'
+ 34: 253, # '"'
+ 35: 253, # '#'
+ 36: 253, # '$'
+ 37: 253, # '%'
+ 38: 253, # '&'
+ 39: 253, # "'"
+ 40: 253, # '('
+ 41: 253, # ')'
+ 42: 253, # '*'
+ 43: 253, # '+'
+ 44: 253, # ','
+ 45: 253, # '-'
+ 46: 253, # '.'
+ 47: 253, # '/'
+ 48: 252, # '0'
+ 49: 252, # '1'
+ 50: 252, # '2'
+ 51: 252, # '3'
+ 52: 252, # '4'
+ 53: 252, # '5'
+ 54: 252, # '6'
+ 55: 252, # '7'
+ 56: 252, # '8'
+ 57: 252, # '9'
+ 58: 253, # ':'
+ 59: 253, # ';'
+ 60: 253, # '<'
+ 61: 253, # '='
+ 62: 253, # '>'
+ 63: 253, # '?'
+ 64: 253, # '@'
+ 65: 77, # 'A'
+ 66: 90, # 'B'
+ 67: 99, # 'C'
+ 68: 100, # 'D'
+ 69: 72, # 'E'
+ 70: 109, # 'F'
+ 71: 107, # 'G'
+ 72: 101, # 'H'
+ 73: 79, # 'I'
+ 74: 185, # 'J'
+ 75: 81, # 'K'
+ 76: 102, # 'L'
+ 77: 76, # 'M'
+ 78: 94, # 'N'
+ 79: 82, # 'O'
+ 80: 110, # 'P'
+ 81: 186, # 'Q'
+ 82: 108, # 'R'
+ 83: 91, # 'S'
+ 84: 74, # 'T'
+ 85: 119, # 'U'
+ 86: 84, # 'V'
+ 87: 96, # 'W'
+ 88: 111, # 'X'
+ 89: 187, # 'Y'
+ 90: 115, # 'Z'
+ 91: 253, # '['
+ 92: 253, # '\\'
+ 93: 253, # ']'
+ 94: 253, # '^'
+ 95: 253, # '_'
+ 96: 253, # '`'
+ 97: 65, # 'a'
+ 98: 69, # 'b'
+ 99: 70, # 'c'
+ 100: 66, # 'd'
+ 101: 63, # 'e'
+ 102: 68, # 'f'
+ 103: 112, # 'g'
+ 104: 103, # 'h'
+ 105: 92, # 'i'
+ 106: 194, # 'j'
+ 107: 104, # 'k'
+ 108: 95, # 'l'
+ 109: 86, # 'm'
+ 110: 87, # 'n'
+ 111: 71, # 'o'
+ 112: 116, # 'p'
+ 113: 195, # 'q'
+ 114: 85, # 'r'
+ 115: 93, # 's'
+ 116: 97, # 't'
+ 117: 113, # 'u'
+ 118: 196, # 'v'
+ 119: 197, # 'w'
+ 120: 198, # 'x'
+ 121: 199, # 'y'
+ 122: 200, # 'z'
+ 123: 253, # '{'
+ 124: 253, # '|'
+ 125: 253, # '}'
+ 126: 253, # '~'
+ 127: 253, # '\x7f'
+ 128: 194, # '\x80'
+ 129: 195, # '\x81'
+ 130: 196, # '\x82'
+ 131: 197, # '\x83'
+ 132: 198, # '\x84'
+ 133: 199, # '\x85'
+ 134: 200, # '\x86'
+ 135: 201, # '\x87'
+ 136: 202, # '\x88'
+ 137: 203, # '\x89'
+ 138: 204, # '\x8a'
+ 139: 205, # '\x8b'
+ 140: 206, # '\x8c'
+ 141: 207, # '\x8d'
+ 142: 208, # '\x8e'
+ 143: 209, # '\x8f'
+ 144: 210, # '\x90'
+ 145: 211, # '\x91'
+ 146: 212, # '\x92'
+ 147: 213, # '\x93'
+ 148: 214, # '\x94'
+ 149: 215, # '\x95'
+ 150: 216, # '\x96'
+ 151: 217, # '\x97'
+ 152: 218, # '\x98'
+ 153: 219, # '\x99'
+ 154: 220, # '\x9a'
+ 155: 221, # '\x9b'
+ 156: 222, # '\x9c'
+ 157: 223, # '\x9d'
+ 158: 224, # '\x9e'
+ 159: 225, # '\x9f'
+ 160: 81, # '\xa0'
+ 161: 226, # 'Ё'
+ 162: 227, # 'Ђ'
+ 163: 228, # 'Ѓ'
+ 164: 229, # 'Є'
+ 165: 230, # 'Ѕ'
+ 166: 105, # 'І'
+ 167: 231, # 'Ї'
+ 168: 232, # 'Ј'
+ 169: 233, # 'Љ'
+ 170: 234, # 'Њ'
+ 171: 235, # 'Ћ'
+ 172: 236, # 'Ќ'
+ 173: 45, # '\xad'
+ 174: 237, # 'Ў'
+ 175: 238, # 'Џ'
+ 176: 31, # 'А'
+ 177: 32, # 'Б'
+ 178: 35, # 'В'
+ 179: 43, # 'Г'
+ 180: 37, # 'Д'
+ 181: 44, # 'Е'
+ 182: 55, # 'Ж'
+ 183: 47, # 'З'
+ 184: 40, # 'И'
+ 185: 59, # 'Й'
+ 186: 33, # 'К'
+ 187: 46, # 'Л'
+ 188: 38, # 'М'
+ 189: 36, # 'Н'
+ 190: 41, # 'О'
+ 191: 30, # 'П'
+ 192: 39, # 'Р'
+ 193: 28, # 'С'
+ 194: 34, # 'Т'
+ 195: 51, # 'У'
+ 196: 48, # 'Ф'
+ 197: 49, # 'Х'
+ 198: 53, # 'Ц'
+ 199: 50, # 'Ч'
+ 200: 54, # 'Ш'
+ 201: 57, # 'Щ'
+ 202: 61, # 'Ъ'
+ 203: 239, # 'Ы'
+ 204: 67, # 'Ь'
+ 205: 240, # 'Э'
+ 206: 60, # 'Ю'
+ 207: 56, # 'Я'
+ 208: 1, # 'а'
+ 209: 18, # 'б'
+ 210: 9, # 'в'
+ 211: 20, # 'г'
+ 212: 11, # 'д'
+ 213: 3, # 'е'
+ 214: 23, # 'ж'
+ 215: 15, # 'з'
+ 216: 2, # 'и'
+ 217: 26, # 'й'
+ 218: 12, # 'к'
+ 219: 10, # 'л'
+ 220: 14, # 'м'
+ 221: 6, # 'н'
+ 222: 4, # 'о'
+ 223: 13, # 'п'
+ 224: 7, # 'р'
+ 225: 8, # 'с'
+ 226: 5, # 'т'
+ 227: 19, # 'у'
+ 228: 29, # 'ф'
+ 229: 25, # 'х'
+ 230: 22, # 'ц'
+ 231: 21, # 'ч'
+ 232: 27, # 'ш'
+ 233: 24, # 'щ'
+ 234: 17, # 'ъ'
+ 235: 75, # 'ы'
+ 236: 52, # 'ь'
+ 237: 241, # 'э'
+ 238: 42, # 'ю'
+ 239: 16, # 'я'
+ 240: 62, # '№'
+ 241: 242, # 'ё'
+ 242: 243, # 'ђ'
+ 243: 244, # 'ѓ'
+ 244: 58, # 'є'
+ 245: 245, # 'ѕ'
+ 246: 98, # 'і'
+ 247: 246, # 'ї'
+ 248: 247, # 'ј'
+ 249: 248, # 'љ'
+ 250: 249, # 'њ'
+ 251: 250, # 'ћ'
+ 252: 251, # 'ќ'
+ 253: 91, # '§'
+ 254: 252, # 'ў'
+ 255: 253, # 'џ'
}
-ISO_8859_5_BULGARIAN_MODEL = SingleByteCharSetModel(charset_name='ISO-8859-5',
- language='Bulgarian',
- char_to_order_map=ISO_8859_5_BULGARIAN_CHAR_TO_ORDER,
- language_model=BULGARIAN_LANG_MODEL,
- typical_positive_ratio=0.969392,
- keep_ascii_letters=False,
- alphabet='АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯабвгдежзийклмнопрстуфхцчшщъьюя')
+ISO_8859_5_BULGARIAN_MODEL = SingleByteCharSetModel(
+ charset_name="ISO-8859-5",
+ language="Bulgarian",
+ char_to_order_map=ISO_8859_5_BULGARIAN_CHAR_TO_ORDER,
+ language_model=BULGARIAN_LANG_MODEL,
+ typical_positive_ratio=0.969392,
+ keep_ascii_letters=False,
+ alphabet="АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯабвгдежзийклмнопрстуфхцчшщъьюя",
+)
WINDOWS_1251_BULGARIAN_CHAR_TO_ORDER = {
- 0: 255, # '\x00'
- 1: 255, # '\x01'
- 2: 255, # '\x02'
- 3: 255, # '\x03'
- 4: 255, # '\x04'
- 5: 255, # '\x05'
- 6: 255, # '\x06'
- 7: 255, # '\x07'
- 8: 255, # '\x08'
- 9: 255, # '\t'
- 10: 254, # '\n'
- 11: 255, # '\x0b'
- 12: 255, # '\x0c'
- 13: 254, # '\r'
- 14: 255, # '\x0e'
- 15: 255, # '\x0f'
- 16: 255, # '\x10'
- 17: 255, # '\x11'
- 18: 255, # '\x12'
- 19: 255, # '\x13'
- 20: 255, # '\x14'
- 21: 255, # '\x15'
- 22: 255, # '\x16'
- 23: 255, # '\x17'
- 24: 255, # '\x18'
- 25: 255, # '\x19'
- 26: 255, # '\x1a'
- 27: 255, # '\x1b'
- 28: 255, # '\x1c'
- 29: 255, # '\x1d'
- 30: 255, # '\x1e'
- 31: 255, # '\x1f'
- 32: 253, # ' '
- 33: 253, # '!'
- 34: 253, # '"'
- 35: 253, # '#'
- 36: 253, # '$'
- 37: 253, # '%'
- 38: 253, # '&'
- 39: 253, # "'"
- 40: 253, # '('
- 41: 253, # ')'
- 42: 253, # '*'
- 43: 253, # '+'
- 44: 253, # ','
- 45: 253, # '-'
- 46: 253, # '.'
- 47: 253, # '/'
- 48: 252, # '0'
- 49: 252, # '1'
- 50: 252, # '2'
- 51: 252, # '3'
- 52: 252, # '4'
- 53: 252, # '5'
- 54: 252, # '6'
- 55: 252, # '7'
- 56: 252, # '8'
- 57: 252, # '9'
- 58: 253, # ':'
- 59: 253, # ';'
- 60: 253, # '<'
- 61: 253, # '='
- 62: 253, # '>'
- 63: 253, # '?'
- 64: 253, # '@'
- 65: 77, # 'A'
- 66: 90, # 'B'
- 67: 99, # 'C'
- 68: 100, # 'D'
- 69: 72, # 'E'
- 70: 109, # 'F'
- 71: 107, # 'G'
- 72: 101, # 'H'
- 73: 79, # 'I'
- 74: 185, # 'J'
- 75: 81, # 'K'
- 76: 102, # 'L'
- 77: 76, # 'M'
- 78: 94, # 'N'
- 79: 82, # 'O'
- 80: 110, # 'P'
- 81: 186, # 'Q'
- 82: 108, # 'R'
- 83: 91, # 'S'
- 84: 74, # 'T'
- 85: 119, # 'U'
- 86: 84, # 'V'
- 87: 96, # 'W'
- 88: 111, # 'X'
- 89: 187, # 'Y'
- 90: 115, # 'Z'
- 91: 253, # '['
- 92: 253, # '\\'
- 93: 253, # ']'
- 94: 253, # '^'
- 95: 253, # '_'
- 96: 253, # '`'
- 97: 65, # 'a'
- 98: 69, # 'b'
- 99: 70, # 'c'
- 100: 66, # 'd'
- 101: 63, # 'e'
- 102: 68, # 'f'
- 103: 112, # 'g'
- 104: 103, # 'h'
- 105: 92, # 'i'
- 106: 194, # 'j'
- 107: 104, # 'k'
- 108: 95, # 'l'
- 109: 86, # 'm'
- 110: 87, # 'n'
- 111: 71, # 'o'
- 112: 116, # 'p'
- 113: 195, # 'q'
- 114: 85, # 'r'
- 115: 93, # 's'
- 116: 97, # 't'
- 117: 113, # 'u'
- 118: 196, # 'v'
- 119: 197, # 'w'
- 120: 198, # 'x'
- 121: 199, # 'y'
- 122: 200, # 'z'
- 123: 253, # '{'
- 124: 253, # '|'
- 125: 253, # '}'
- 126: 253, # '~'
- 127: 253, # '\x7f'
- 128: 206, # 'Ђ'
- 129: 207, # 'Ѓ'
- 130: 208, # '‚'
- 131: 209, # 'ѓ'
- 132: 210, # '„'
- 133: 211, # '…'
- 134: 212, # '†'
- 135: 213, # '‡'
- 136: 120, # '€'
- 137: 214, # '‰'
- 138: 215, # 'Љ'
- 139: 216, # '‹'
- 140: 217, # 'Њ'
- 141: 218, # 'Ќ'
- 142: 219, # 'Ћ'
- 143: 220, # 'Џ'
- 144: 221, # 'ђ'
- 145: 78, # '‘'
- 146: 64, # '’'
- 147: 83, # '“'
- 148: 121, # '”'
- 149: 98, # '•'
- 150: 117, # '–'
- 151: 105, # '—'
- 152: 222, # None
- 153: 223, # '™'
- 154: 224, # 'љ'
- 155: 225, # '›'
- 156: 226, # 'њ'
- 157: 227, # 'ќ'
- 158: 228, # 'ћ'
- 159: 229, # 'џ'
- 160: 88, # '\xa0'
- 161: 230, # 'Ў'
- 162: 231, # 'ў'
- 163: 232, # 'Ј'
- 164: 233, # '¤'
- 165: 122, # 'Ґ'
- 166: 89, # '¦'
- 167: 106, # '§'
- 168: 234, # 'Ё'
- 169: 235, # '©'
- 170: 236, # 'Є'
- 171: 237, # '«'
- 172: 238, # '¬'
- 173: 45, # '\xad'
- 174: 239, # '®'
- 175: 240, # 'Ї'
- 176: 73, # '°'
- 177: 80, # '±'
- 178: 118, # 'І'
- 179: 114, # 'і'
- 180: 241, # 'ґ'
- 181: 242, # 'µ'
- 182: 243, # '¶'
- 183: 244, # '·'
- 184: 245, # 'ё'
- 185: 62, # '№'
- 186: 58, # 'є'
- 187: 246, # '»'
- 188: 247, # 'ј'
- 189: 248, # 'Ѕ'
- 190: 249, # 'ѕ'
- 191: 250, # 'ї'
- 192: 31, # 'А'
- 193: 32, # 'Б'
- 194: 35, # 'В'
- 195: 43, # 'Г'
- 196: 37, # 'Д'
- 197: 44, # 'Е'
- 198: 55, # 'Ж'
- 199: 47, # 'З'
- 200: 40, # 'И'
- 201: 59, # 'Й'
- 202: 33, # 'К'
- 203: 46, # 'Л'
- 204: 38, # 'М'
- 205: 36, # 'Н'
- 206: 41, # 'О'
- 207: 30, # 'П'
- 208: 39, # 'Р'
- 209: 28, # 'С'
- 210: 34, # 'Т'
- 211: 51, # 'У'
- 212: 48, # 'Ф'
- 213: 49, # 'Х'
- 214: 53, # 'Ц'
- 215: 50, # 'Ч'
- 216: 54, # 'Ш'
- 217: 57, # 'Щ'
- 218: 61, # 'Ъ'
- 219: 251, # 'Ы'
- 220: 67, # 'Ь'
- 221: 252, # 'Э'
- 222: 60, # 'Ю'
- 223: 56, # 'Я'
- 224: 1, # 'а'
- 225: 18, # 'б'
- 226: 9, # 'в'
- 227: 20, # 'г'
- 228: 11, # 'д'
- 229: 3, # 'е'
- 230: 23, # 'ж'
- 231: 15, # 'з'
- 232: 2, # 'и'
- 233: 26, # 'й'
- 234: 12, # 'к'
- 235: 10, # 'л'
- 236: 14, # 'м'
- 237: 6, # 'н'
- 238: 4, # 'о'
- 239: 13, # 'п'
- 240: 7, # 'р'
- 241: 8, # 'с'
- 242: 5, # 'т'
- 243: 19, # 'у'
- 244: 29, # 'ф'
- 245: 25, # 'х'
- 246: 22, # 'ц'
- 247: 21, # 'ч'
- 248: 27, # 'ш'
- 249: 24, # 'щ'
- 250: 17, # 'ъ'
- 251: 75, # 'ы'
- 252: 52, # 'ь'
- 253: 253, # 'э'
- 254: 42, # 'ю'
- 255: 16, # 'я'
+ 0: 255, # '\x00'
+ 1: 255, # '\x01'
+ 2: 255, # '\x02'
+ 3: 255, # '\x03'
+ 4: 255, # '\x04'
+ 5: 255, # '\x05'
+ 6: 255, # '\x06'
+ 7: 255, # '\x07'
+ 8: 255, # '\x08'
+ 9: 255, # '\t'
+ 10: 254, # '\n'
+ 11: 255, # '\x0b'
+ 12: 255, # '\x0c'
+ 13: 254, # '\r'
+ 14: 255, # '\x0e'
+ 15: 255, # '\x0f'
+ 16: 255, # '\x10'
+ 17: 255, # '\x11'
+ 18: 255, # '\x12'
+ 19: 255, # '\x13'
+ 20: 255, # '\x14'
+ 21: 255, # '\x15'
+ 22: 255, # '\x16'
+ 23: 255, # '\x17'
+ 24: 255, # '\x18'
+ 25: 255, # '\x19'
+ 26: 255, # '\x1a'
+ 27: 255, # '\x1b'
+ 28: 255, # '\x1c'
+ 29: 255, # '\x1d'
+ 30: 255, # '\x1e'
+ 31: 255, # '\x1f'
+ 32: 253, # ' '
+ 33: 253, # '!'
+ 34: 253, # '"'
+ 35: 253, # '#'
+ 36: 253, # '$'
+ 37: 253, # '%'
+ 38: 253, # '&'
+ 39: 253, # "'"
+ 40: 253, # '('
+ 41: 253, # ')'
+ 42: 253, # '*'
+ 43: 253, # '+'
+ 44: 253, # ','
+ 45: 253, # '-'
+ 46: 253, # '.'
+ 47: 253, # '/'
+ 48: 252, # '0'
+ 49: 252, # '1'
+ 50: 252, # '2'
+ 51: 252, # '3'
+ 52: 252, # '4'
+ 53: 252, # '5'
+ 54: 252, # '6'
+ 55: 252, # '7'
+ 56: 252, # '8'
+ 57: 252, # '9'
+ 58: 253, # ':'
+ 59: 253, # ';'
+ 60: 253, # '<'
+ 61: 253, # '='
+ 62: 253, # '>'
+ 63: 253, # '?'
+ 64: 253, # '@'
+ 65: 77, # 'A'
+ 66: 90, # 'B'
+ 67: 99, # 'C'
+ 68: 100, # 'D'
+ 69: 72, # 'E'
+ 70: 109, # 'F'
+ 71: 107, # 'G'
+ 72: 101, # 'H'
+ 73: 79, # 'I'
+ 74: 185, # 'J'
+ 75: 81, # 'K'
+ 76: 102, # 'L'
+ 77: 76, # 'M'
+ 78: 94, # 'N'
+ 79: 82, # 'O'
+ 80: 110, # 'P'
+ 81: 186, # 'Q'
+ 82: 108, # 'R'
+ 83: 91, # 'S'
+ 84: 74, # 'T'
+ 85: 119, # 'U'
+ 86: 84, # 'V'
+ 87: 96, # 'W'
+ 88: 111, # 'X'
+ 89: 187, # 'Y'
+ 90: 115, # 'Z'
+ 91: 253, # '['
+ 92: 253, # '\\'
+ 93: 253, # ']'
+ 94: 253, # '^'
+ 95: 253, # '_'
+ 96: 253, # '`'
+ 97: 65, # 'a'
+ 98: 69, # 'b'
+ 99: 70, # 'c'
+ 100: 66, # 'd'
+ 101: 63, # 'e'
+ 102: 68, # 'f'
+ 103: 112, # 'g'
+ 104: 103, # 'h'
+ 105: 92, # 'i'
+ 106: 194, # 'j'
+ 107: 104, # 'k'
+ 108: 95, # 'l'
+ 109: 86, # 'm'
+ 110: 87, # 'n'
+ 111: 71, # 'o'
+ 112: 116, # 'p'
+ 113: 195, # 'q'
+ 114: 85, # 'r'
+ 115: 93, # 's'
+ 116: 97, # 't'
+ 117: 113, # 'u'
+ 118: 196, # 'v'
+ 119: 197, # 'w'
+ 120: 198, # 'x'
+ 121: 199, # 'y'
+ 122: 200, # 'z'
+ 123: 253, # '{'
+ 124: 253, # '|'
+ 125: 253, # '}'
+ 126: 253, # '~'
+ 127: 253, # '\x7f'
+ 128: 206, # 'Ђ'
+ 129: 207, # 'Ѓ'
+ 130: 208, # '‚'
+ 131: 209, # 'ѓ'
+ 132: 210, # '„'
+ 133: 211, # '…'
+ 134: 212, # '†'
+ 135: 213, # '‡'
+ 136: 120, # '€'
+ 137: 214, # '‰'
+ 138: 215, # 'Љ'
+ 139: 216, # '‹'
+ 140: 217, # 'Њ'
+ 141: 218, # 'Ќ'
+ 142: 219, # 'Ћ'
+ 143: 220, # 'Џ'
+ 144: 221, # 'ђ'
+ 145: 78, # '‘'
+ 146: 64, # '’'
+ 147: 83, # '“'
+ 148: 121, # '”'
+ 149: 98, # '•'
+ 150: 117, # '–'
+ 151: 105, # '—'
+ 152: 222, # None
+ 153: 223, # '™'
+ 154: 224, # 'љ'
+ 155: 225, # '›'
+ 156: 226, # 'њ'
+ 157: 227, # 'ќ'
+ 158: 228, # 'ћ'
+ 159: 229, # 'џ'
+ 160: 88, # '\xa0'
+ 161: 230, # 'Ў'
+ 162: 231, # 'ў'
+ 163: 232, # 'Ј'
+ 164: 233, # '¤'
+ 165: 122, # 'Ґ'
+ 166: 89, # '¦'
+ 167: 106, # '§'
+ 168: 234, # 'Ё'
+ 169: 235, # '©'
+ 170: 236, # 'Є'
+ 171: 237, # '«'
+ 172: 238, # '¬'
+ 173: 45, # '\xad'
+ 174: 239, # '®'
+ 175: 240, # 'Ї'
+ 176: 73, # '°'
+ 177: 80, # '±'
+ 178: 118, # 'І'
+ 179: 114, # 'і'
+ 180: 241, # 'ґ'
+ 181: 242, # 'µ'
+ 182: 243, # '¶'
+ 183: 244, # '·'
+ 184: 245, # 'ё'
+ 185: 62, # '№'
+ 186: 58, # 'є'
+ 187: 246, # '»'
+ 188: 247, # 'ј'
+ 189: 248, # 'Ѕ'
+ 190: 249, # 'ѕ'
+ 191: 250, # 'ї'
+ 192: 31, # 'А'
+ 193: 32, # 'Б'
+ 194: 35, # 'В'
+ 195: 43, # 'Г'
+ 196: 37, # 'Д'
+ 197: 44, # 'Е'
+ 198: 55, # 'Ж'
+ 199: 47, # 'З'
+ 200: 40, # 'И'
+ 201: 59, # 'Й'
+ 202: 33, # 'К'
+ 203: 46, # 'Л'
+ 204: 38, # 'М'
+ 205: 36, # 'Н'
+ 206: 41, # 'О'
+ 207: 30, # 'П'
+ 208: 39, # 'Р'
+ 209: 28, # 'С'
+ 210: 34, # 'Т'
+ 211: 51, # 'У'
+ 212: 48, # 'Ф'
+ 213: 49, # 'Х'
+ 214: 53, # 'Ц'
+ 215: 50, # 'Ч'
+ 216: 54, # 'Ш'
+ 217: 57, # 'Щ'
+ 218: 61, # 'Ъ'
+ 219: 251, # 'Ы'
+ 220: 67, # 'Ь'
+ 221: 252, # 'Э'
+ 222: 60, # 'Ю'
+ 223: 56, # 'Я'
+ 224: 1, # 'а'
+ 225: 18, # 'б'
+ 226: 9, # 'в'
+ 227: 20, # 'г'
+ 228: 11, # 'д'
+ 229: 3, # 'е'
+ 230: 23, # 'ж'
+ 231: 15, # 'з'
+ 232: 2, # 'и'
+ 233: 26, # 'й'
+ 234: 12, # 'к'
+ 235: 10, # 'л'
+ 236: 14, # 'м'
+ 237: 6, # 'н'
+ 238: 4, # 'о'
+ 239: 13, # 'п'
+ 240: 7, # 'р'
+ 241: 8, # 'с'
+ 242: 5, # 'т'
+ 243: 19, # 'у'
+ 244: 29, # 'ф'
+ 245: 25, # 'х'
+ 246: 22, # 'ц'
+ 247: 21, # 'ч'
+ 248: 27, # 'ш'
+ 249: 24, # 'щ'
+ 250: 17, # 'ъ'
+ 251: 75, # 'ы'
+ 252: 52, # 'ь'
+ 253: 253, # 'э'
+ 254: 42, # 'ю'
+ 255: 16, # 'я'
}
-WINDOWS_1251_BULGARIAN_MODEL = SingleByteCharSetModel(charset_name='windows-1251',
- language='Bulgarian',
- char_to_order_map=WINDOWS_1251_BULGARIAN_CHAR_TO_ORDER,
- language_model=BULGARIAN_LANG_MODEL,
- typical_positive_ratio=0.969392,
- keep_ascii_letters=False,
- alphabet='АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯабвгдежзийклмнопрстуфхцчшщъьюя')
-
+WINDOWS_1251_BULGARIAN_MODEL = SingleByteCharSetModel(
+ charset_name="windows-1251",
+ language="Bulgarian",
+ char_to_order_map=WINDOWS_1251_BULGARIAN_CHAR_TO_ORDER,
+ language_model=BULGARIAN_LANG_MODEL,
+ typical_positive_ratio=0.969392,
+ keep_ascii_letters=False,
+ alphabet="АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯабвгдежзийклмнопрстуфхцчшщъьюя",
+)
diff --git a/src/pip/_vendor/chardet/langgreekmodel.py b/src/pip/_vendor/chardet/langgreekmodel.py
index d99528ede..cfb8639e5 100644
--- a/src/pip/_vendor/chardet/langgreekmodel.py
+++ b/src/pip/_vendor/chardet/langgreekmodel.py
@@ -1,9 +1,5 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
from pip._vendor.chardet.sbcharsetprober import SingleByteCharSetModel
-
# 3: Positive
# 2: Likely
# 1: Unlikely
@@ -3863,536 +3859,539 @@ GREEK_LANG_MODEL = {
# Character Mapping Table(s):
WINDOWS_1253_GREEK_CHAR_TO_ORDER = {
- 0: 255, # '\x00'
- 1: 255, # '\x01'
- 2: 255, # '\x02'
- 3: 255, # '\x03'
- 4: 255, # '\x04'
- 5: 255, # '\x05'
- 6: 255, # '\x06'
- 7: 255, # '\x07'
- 8: 255, # '\x08'
- 9: 255, # '\t'
- 10: 254, # '\n'
- 11: 255, # '\x0b'
- 12: 255, # '\x0c'
- 13: 254, # '\r'
- 14: 255, # '\x0e'
- 15: 255, # '\x0f'
- 16: 255, # '\x10'
- 17: 255, # '\x11'
- 18: 255, # '\x12'
- 19: 255, # '\x13'
- 20: 255, # '\x14'
- 21: 255, # '\x15'
- 22: 255, # '\x16'
- 23: 255, # '\x17'
- 24: 255, # '\x18'
- 25: 255, # '\x19'
- 26: 255, # '\x1a'
- 27: 255, # '\x1b'
- 28: 255, # '\x1c'
- 29: 255, # '\x1d'
- 30: 255, # '\x1e'
- 31: 255, # '\x1f'
- 32: 253, # ' '
- 33: 253, # '!'
- 34: 253, # '"'
- 35: 253, # '#'
- 36: 253, # '$'
- 37: 253, # '%'
- 38: 253, # '&'
- 39: 253, # "'"
- 40: 253, # '('
- 41: 253, # ')'
- 42: 253, # '*'
- 43: 253, # '+'
- 44: 253, # ','
- 45: 253, # '-'
- 46: 253, # '.'
- 47: 253, # '/'
- 48: 252, # '0'
- 49: 252, # '1'
- 50: 252, # '2'
- 51: 252, # '3'
- 52: 252, # '4'
- 53: 252, # '5'
- 54: 252, # '6'
- 55: 252, # '7'
- 56: 252, # '8'
- 57: 252, # '9'
- 58: 253, # ':'
- 59: 253, # ';'
- 60: 253, # '<'
- 61: 253, # '='
- 62: 253, # '>'
- 63: 253, # '?'
- 64: 253, # '@'
- 65: 82, # 'A'
- 66: 100, # 'B'
- 67: 104, # 'C'
- 68: 94, # 'D'
- 69: 98, # 'E'
- 70: 101, # 'F'
- 71: 116, # 'G'
- 72: 102, # 'H'
- 73: 111, # 'I'
- 74: 187, # 'J'
- 75: 117, # 'K'
- 76: 92, # 'L'
- 77: 88, # 'M'
- 78: 113, # 'N'
- 79: 85, # 'O'
- 80: 79, # 'P'
- 81: 118, # 'Q'
- 82: 105, # 'R'
- 83: 83, # 'S'
- 84: 67, # 'T'
- 85: 114, # 'U'
- 86: 119, # 'V'
- 87: 95, # 'W'
- 88: 99, # 'X'
- 89: 109, # 'Y'
- 90: 188, # 'Z'
- 91: 253, # '['
- 92: 253, # '\\'
- 93: 253, # ']'
- 94: 253, # '^'
- 95: 253, # '_'
- 96: 253, # '`'
- 97: 72, # 'a'
- 98: 70, # 'b'
- 99: 80, # 'c'
- 100: 81, # 'd'
- 101: 60, # 'e'
- 102: 96, # 'f'
- 103: 93, # 'g'
- 104: 89, # 'h'
- 105: 68, # 'i'
- 106: 120, # 'j'
- 107: 97, # 'k'
- 108: 77, # 'l'
- 109: 86, # 'm'
- 110: 69, # 'n'
- 111: 55, # 'o'
- 112: 78, # 'p'
- 113: 115, # 'q'
- 114: 65, # 'r'
- 115: 66, # 's'
- 116: 58, # 't'
- 117: 76, # 'u'
- 118: 106, # 'v'
- 119: 103, # 'w'
- 120: 87, # 'x'
- 121: 107, # 'y'
- 122: 112, # 'z'
- 123: 253, # '{'
- 124: 253, # '|'
- 125: 253, # '}'
- 126: 253, # '~'
- 127: 253, # '\x7f'
- 128: 255, # '€'
- 129: 255, # None
- 130: 255, # '‚'
- 131: 255, # 'ƒ'
- 132: 255, # '„'
- 133: 255, # '…'
- 134: 255, # '†'
- 135: 255, # '‡'
- 136: 255, # None
- 137: 255, # '‰'
- 138: 255, # None
- 139: 255, # '‹'
- 140: 255, # None
- 141: 255, # None
- 142: 255, # None
- 143: 255, # None
- 144: 255, # None
- 145: 255, # '‘'
- 146: 255, # '’'
- 147: 255, # '“'
- 148: 255, # '”'
- 149: 255, # '•'
- 150: 255, # '–'
- 151: 255, # '—'
- 152: 255, # None
- 153: 255, # '™'
- 154: 255, # None
- 155: 255, # '›'
- 156: 255, # None
- 157: 255, # None
- 158: 255, # None
- 159: 255, # None
- 160: 253, # '\xa0'
- 161: 233, # '΅'
- 162: 61, # 'Ά'
- 163: 253, # '£'
- 164: 253, # '¤'
- 165: 253, # '¥'
- 166: 253, # '¦'
- 167: 253, # '§'
- 168: 253, # '¨'
- 169: 253, # '©'
- 170: 253, # None
- 171: 253, # '«'
- 172: 253, # '¬'
- 173: 74, # '\xad'
- 174: 253, # '®'
- 175: 253, # '―'
- 176: 253, # '°'
- 177: 253, # '±'
- 178: 253, # '²'
- 179: 253, # '³'
- 180: 247, # '΄'
- 181: 253, # 'µ'
- 182: 253, # '¶'
- 183: 36, # '·'
- 184: 46, # 'Έ'
- 185: 71, # 'Ή'
- 186: 73, # 'Ί'
- 187: 253, # '»'
- 188: 54, # 'Ό'
- 189: 253, # '½'
- 190: 108, # 'Ύ'
- 191: 123, # 'Ώ'
- 192: 110, # 'ΐ'
- 193: 31, # 'Α'
- 194: 51, # 'Β'
- 195: 43, # 'Γ'
- 196: 41, # 'Δ'
- 197: 34, # 'Ε'
- 198: 91, # 'Ζ'
- 199: 40, # 'Η'
- 200: 52, # 'Θ'
- 201: 47, # 'Ι'
- 202: 44, # 'Κ'
- 203: 53, # 'Λ'
- 204: 38, # 'Μ'
- 205: 49, # 'Ν'
- 206: 59, # 'Ξ'
- 207: 39, # 'Ο'
- 208: 35, # 'Π'
- 209: 48, # 'Ρ'
- 210: 250, # None
- 211: 37, # 'Σ'
- 212: 33, # 'Τ'
- 213: 45, # 'Υ'
- 214: 56, # 'Φ'
- 215: 50, # 'Χ'
- 216: 84, # 'Ψ'
- 217: 57, # 'Ω'
- 218: 120, # 'Ϊ'
- 219: 121, # 'Ϋ'
- 220: 17, # 'ά'
- 221: 18, # 'έ'
- 222: 22, # 'ή'
- 223: 15, # 'ί'
- 224: 124, # 'ΰ'
- 225: 1, # 'α'
- 226: 29, # 'β'
- 227: 20, # 'γ'
- 228: 21, # 'δ'
- 229: 3, # 'ε'
- 230: 32, # 'ζ'
- 231: 13, # 'η'
- 232: 25, # 'θ'
- 233: 5, # 'ι'
- 234: 11, # 'κ'
- 235: 16, # 'λ'
- 236: 10, # 'μ'
- 237: 6, # 'ν'
- 238: 30, # 'ξ'
- 239: 4, # 'ο'
- 240: 9, # 'π'
- 241: 8, # 'ρ'
- 242: 14, # 'ς'
- 243: 7, # 'σ'
- 244: 2, # 'τ'
- 245: 12, # 'υ'
- 246: 28, # 'φ'
- 247: 23, # 'χ'
- 248: 42, # 'ψ'
- 249: 24, # 'ω'
- 250: 64, # 'ϊ'
- 251: 75, # 'ϋ'
- 252: 19, # 'ό'
- 253: 26, # 'ύ'
- 254: 27, # 'ώ'
- 255: 253, # None
+ 0: 255, # '\x00'
+ 1: 255, # '\x01'
+ 2: 255, # '\x02'
+ 3: 255, # '\x03'
+ 4: 255, # '\x04'
+ 5: 255, # '\x05'
+ 6: 255, # '\x06'
+ 7: 255, # '\x07'
+ 8: 255, # '\x08'
+ 9: 255, # '\t'
+ 10: 254, # '\n'
+ 11: 255, # '\x0b'
+ 12: 255, # '\x0c'
+ 13: 254, # '\r'
+ 14: 255, # '\x0e'
+ 15: 255, # '\x0f'
+ 16: 255, # '\x10'
+ 17: 255, # '\x11'
+ 18: 255, # '\x12'
+ 19: 255, # '\x13'
+ 20: 255, # '\x14'
+ 21: 255, # '\x15'
+ 22: 255, # '\x16'
+ 23: 255, # '\x17'
+ 24: 255, # '\x18'
+ 25: 255, # '\x19'
+ 26: 255, # '\x1a'
+ 27: 255, # '\x1b'
+ 28: 255, # '\x1c'
+ 29: 255, # '\x1d'
+ 30: 255, # '\x1e'
+ 31: 255, # '\x1f'
+ 32: 253, # ' '
+ 33: 253, # '!'
+ 34: 253, # '"'
+ 35: 253, # '#'
+ 36: 253, # '$'
+ 37: 253, # '%'
+ 38: 253, # '&'
+ 39: 253, # "'"
+ 40: 253, # '('
+ 41: 253, # ')'
+ 42: 253, # '*'
+ 43: 253, # '+'
+ 44: 253, # ','
+ 45: 253, # '-'
+ 46: 253, # '.'
+ 47: 253, # '/'
+ 48: 252, # '0'
+ 49: 252, # '1'
+ 50: 252, # '2'
+ 51: 252, # '3'
+ 52: 252, # '4'
+ 53: 252, # '5'
+ 54: 252, # '6'
+ 55: 252, # '7'
+ 56: 252, # '8'
+ 57: 252, # '9'
+ 58: 253, # ':'
+ 59: 253, # ';'
+ 60: 253, # '<'
+ 61: 253, # '='
+ 62: 253, # '>'
+ 63: 253, # '?'
+ 64: 253, # '@'
+ 65: 82, # 'A'
+ 66: 100, # 'B'
+ 67: 104, # 'C'
+ 68: 94, # 'D'
+ 69: 98, # 'E'
+ 70: 101, # 'F'
+ 71: 116, # 'G'
+ 72: 102, # 'H'
+ 73: 111, # 'I'
+ 74: 187, # 'J'
+ 75: 117, # 'K'
+ 76: 92, # 'L'
+ 77: 88, # 'M'
+ 78: 113, # 'N'
+ 79: 85, # 'O'
+ 80: 79, # 'P'
+ 81: 118, # 'Q'
+ 82: 105, # 'R'
+ 83: 83, # 'S'
+ 84: 67, # 'T'
+ 85: 114, # 'U'
+ 86: 119, # 'V'
+ 87: 95, # 'W'
+ 88: 99, # 'X'
+ 89: 109, # 'Y'
+ 90: 188, # 'Z'
+ 91: 253, # '['
+ 92: 253, # '\\'
+ 93: 253, # ']'
+ 94: 253, # '^'
+ 95: 253, # '_'
+ 96: 253, # '`'
+ 97: 72, # 'a'
+ 98: 70, # 'b'
+ 99: 80, # 'c'
+ 100: 81, # 'd'
+ 101: 60, # 'e'
+ 102: 96, # 'f'
+ 103: 93, # 'g'
+ 104: 89, # 'h'
+ 105: 68, # 'i'
+ 106: 120, # 'j'
+ 107: 97, # 'k'
+ 108: 77, # 'l'
+ 109: 86, # 'm'
+ 110: 69, # 'n'
+ 111: 55, # 'o'
+ 112: 78, # 'p'
+ 113: 115, # 'q'
+ 114: 65, # 'r'
+ 115: 66, # 's'
+ 116: 58, # 't'
+ 117: 76, # 'u'
+ 118: 106, # 'v'
+ 119: 103, # 'w'
+ 120: 87, # 'x'
+ 121: 107, # 'y'
+ 122: 112, # 'z'
+ 123: 253, # '{'
+ 124: 253, # '|'
+ 125: 253, # '}'
+ 126: 253, # '~'
+ 127: 253, # '\x7f'
+ 128: 255, # '€'
+ 129: 255, # None
+ 130: 255, # '‚'
+ 131: 255, # 'ƒ'
+ 132: 255, # '„'
+ 133: 255, # '…'
+ 134: 255, # '†'
+ 135: 255, # '‡'
+ 136: 255, # None
+ 137: 255, # '‰'
+ 138: 255, # None
+ 139: 255, # '‹'
+ 140: 255, # None
+ 141: 255, # None
+ 142: 255, # None
+ 143: 255, # None
+ 144: 255, # None
+ 145: 255, # '‘'
+ 146: 255, # '’'
+ 147: 255, # '“'
+ 148: 255, # '”'
+ 149: 255, # '•'
+ 150: 255, # '–'
+ 151: 255, # '—'
+ 152: 255, # None
+ 153: 255, # '™'
+ 154: 255, # None
+ 155: 255, # '›'
+ 156: 255, # None
+ 157: 255, # None
+ 158: 255, # None
+ 159: 255, # None
+ 160: 253, # '\xa0'
+ 161: 233, # '΅'
+ 162: 61, # 'Ά'
+ 163: 253, # '£'
+ 164: 253, # '¤'
+ 165: 253, # '¥'
+ 166: 253, # '¦'
+ 167: 253, # '§'
+ 168: 253, # '¨'
+ 169: 253, # '©'
+ 170: 253, # None
+ 171: 253, # '«'
+ 172: 253, # '¬'
+ 173: 74, # '\xad'
+ 174: 253, # '®'
+ 175: 253, # '―'
+ 176: 253, # '°'
+ 177: 253, # '±'
+ 178: 253, # '²'
+ 179: 253, # '³'
+ 180: 247, # '΄'
+ 181: 253, # 'µ'
+ 182: 253, # '¶'
+ 183: 36, # '·'
+ 184: 46, # 'Έ'
+ 185: 71, # 'Ή'
+ 186: 73, # 'Ί'
+ 187: 253, # '»'
+ 188: 54, # 'Ό'
+ 189: 253, # '½'
+ 190: 108, # 'Ύ'
+ 191: 123, # 'Ώ'
+ 192: 110, # 'ΐ'
+ 193: 31, # 'Α'
+ 194: 51, # 'Β'
+ 195: 43, # 'Γ'
+ 196: 41, # 'Δ'
+ 197: 34, # 'Ε'
+ 198: 91, # 'Ζ'
+ 199: 40, # 'Η'
+ 200: 52, # 'Θ'
+ 201: 47, # 'Ι'
+ 202: 44, # 'Κ'
+ 203: 53, # 'Λ'
+ 204: 38, # 'Μ'
+ 205: 49, # 'Ν'
+ 206: 59, # 'Ξ'
+ 207: 39, # 'Ο'
+ 208: 35, # 'Π'
+ 209: 48, # 'Ρ'
+ 210: 250, # None
+ 211: 37, # 'Σ'
+ 212: 33, # 'Τ'
+ 213: 45, # 'Υ'
+ 214: 56, # 'Φ'
+ 215: 50, # 'Χ'
+ 216: 84, # 'Ψ'
+ 217: 57, # 'Ω'
+ 218: 120, # 'Ϊ'
+ 219: 121, # 'Ϋ'
+ 220: 17, # 'ά'
+ 221: 18, # 'έ'
+ 222: 22, # 'ή'
+ 223: 15, # 'ί'
+ 224: 124, # 'ΰ'
+ 225: 1, # 'α'
+ 226: 29, # 'β'
+ 227: 20, # 'γ'
+ 228: 21, # 'δ'
+ 229: 3, # 'ε'
+ 230: 32, # 'ζ'
+ 231: 13, # 'η'
+ 232: 25, # 'θ'
+ 233: 5, # 'ι'
+ 234: 11, # 'κ'
+ 235: 16, # 'λ'
+ 236: 10, # 'μ'
+ 237: 6, # 'ν'
+ 238: 30, # 'ξ'
+ 239: 4, # 'ο'
+ 240: 9, # 'π'
+ 241: 8, # 'ρ'
+ 242: 14, # 'ς'
+ 243: 7, # 'σ'
+ 244: 2, # 'τ'
+ 245: 12, # 'υ'
+ 246: 28, # 'φ'
+ 247: 23, # 'χ'
+ 248: 42, # 'ψ'
+ 249: 24, # 'ω'
+ 250: 64, # 'ϊ'
+ 251: 75, # 'ϋ'
+ 252: 19, # 'ό'
+ 253: 26, # 'ύ'
+ 254: 27, # 'ώ'
+ 255: 253, # None
}
-WINDOWS_1253_GREEK_MODEL = SingleByteCharSetModel(charset_name='windows-1253',
- language='Greek',
- char_to_order_map=WINDOWS_1253_GREEK_CHAR_TO_ORDER,
- language_model=GREEK_LANG_MODEL,
- typical_positive_ratio=0.982851,
- keep_ascii_letters=False,
- alphabet='ΆΈΉΊΌΎΏΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩάέήίαβγδεζηθικλμνξοπρςστυφχψωόύώ')
+WINDOWS_1253_GREEK_MODEL = SingleByteCharSetModel(
+ charset_name="windows-1253",
+ language="Greek",
+ char_to_order_map=WINDOWS_1253_GREEK_CHAR_TO_ORDER,
+ language_model=GREEK_LANG_MODEL,
+ typical_positive_ratio=0.982851,
+ keep_ascii_letters=False,
+ alphabet="ΆΈΉΊΌΎΏΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩάέήίαβγδεζηθικλμνξοπρςστυφχψωόύώ",
+)
ISO_8859_7_GREEK_CHAR_TO_ORDER = {
- 0: 255, # '\x00'
- 1: 255, # '\x01'
- 2: 255, # '\x02'
- 3: 255, # '\x03'
- 4: 255, # '\x04'
- 5: 255, # '\x05'
- 6: 255, # '\x06'
- 7: 255, # '\x07'
- 8: 255, # '\x08'
- 9: 255, # '\t'
- 10: 254, # '\n'
- 11: 255, # '\x0b'
- 12: 255, # '\x0c'
- 13: 254, # '\r'
- 14: 255, # '\x0e'
- 15: 255, # '\x0f'
- 16: 255, # '\x10'
- 17: 255, # '\x11'
- 18: 255, # '\x12'
- 19: 255, # '\x13'
- 20: 255, # '\x14'
- 21: 255, # '\x15'
- 22: 255, # '\x16'
- 23: 255, # '\x17'
- 24: 255, # '\x18'
- 25: 255, # '\x19'
- 26: 255, # '\x1a'
- 27: 255, # '\x1b'
- 28: 255, # '\x1c'
- 29: 255, # '\x1d'
- 30: 255, # '\x1e'
- 31: 255, # '\x1f'
- 32: 253, # ' '
- 33: 253, # '!'
- 34: 253, # '"'
- 35: 253, # '#'
- 36: 253, # '$'
- 37: 253, # '%'
- 38: 253, # '&'
- 39: 253, # "'"
- 40: 253, # '('
- 41: 253, # ')'
- 42: 253, # '*'
- 43: 253, # '+'
- 44: 253, # ','
- 45: 253, # '-'
- 46: 253, # '.'
- 47: 253, # '/'
- 48: 252, # '0'
- 49: 252, # '1'
- 50: 252, # '2'
- 51: 252, # '3'
- 52: 252, # '4'
- 53: 252, # '5'
- 54: 252, # '6'
- 55: 252, # '7'
- 56: 252, # '8'
- 57: 252, # '9'
- 58: 253, # ':'
- 59: 253, # ';'
- 60: 253, # '<'
- 61: 253, # '='
- 62: 253, # '>'
- 63: 253, # '?'
- 64: 253, # '@'
- 65: 82, # 'A'
- 66: 100, # 'B'
- 67: 104, # 'C'
- 68: 94, # 'D'
- 69: 98, # 'E'
- 70: 101, # 'F'
- 71: 116, # 'G'
- 72: 102, # 'H'
- 73: 111, # 'I'
- 74: 187, # 'J'
- 75: 117, # 'K'
- 76: 92, # 'L'
- 77: 88, # 'M'
- 78: 113, # 'N'
- 79: 85, # 'O'
- 80: 79, # 'P'
- 81: 118, # 'Q'
- 82: 105, # 'R'
- 83: 83, # 'S'
- 84: 67, # 'T'
- 85: 114, # 'U'
- 86: 119, # 'V'
- 87: 95, # 'W'
- 88: 99, # 'X'
- 89: 109, # 'Y'
- 90: 188, # 'Z'
- 91: 253, # '['
- 92: 253, # '\\'
- 93: 253, # ']'
- 94: 253, # '^'
- 95: 253, # '_'
- 96: 253, # '`'
- 97: 72, # 'a'
- 98: 70, # 'b'
- 99: 80, # 'c'
- 100: 81, # 'd'
- 101: 60, # 'e'
- 102: 96, # 'f'
- 103: 93, # 'g'
- 104: 89, # 'h'
- 105: 68, # 'i'
- 106: 120, # 'j'
- 107: 97, # 'k'
- 108: 77, # 'l'
- 109: 86, # 'm'
- 110: 69, # 'n'
- 111: 55, # 'o'
- 112: 78, # 'p'
- 113: 115, # 'q'
- 114: 65, # 'r'
- 115: 66, # 's'
- 116: 58, # 't'
- 117: 76, # 'u'
- 118: 106, # 'v'
- 119: 103, # 'w'
- 120: 87, # 'x'
- 121: 107, # 'y'
- 122: 112, # 'z'
- 123: 253, # '{'
- 124: 253, # '|'
- 125: 253, # '}'
- 126: 253, # '~'
- 127: 253, # '\x7f'
- 128: 255, # '\x80'
- 129: 255, # '\x81'
- 130: 255, # '\x82'
- 131: 255, # '\x83'
- 132: 255, # '\x84'
- 133: 255, # '\x85'
- 134: 255, # '\x86'
- 135: 255, # '\x87'
- 136: 255, # '\x88'
- 137: 255, # '\x89'
- 138: 255, # '\x8a'
- 139: 255, # '\x8b'
- 140: 255, # '\x8c'
- 141: 255, # '\x8d'
- 142: 255, # '\x8e'
- 143: 255, # '\x8f'
- 144: 255, # '\x90'
- 145: 255, # '\x91'
- 146: 255, # '\x92'
- 147: 255, # '\x93'
- 148: 255, # '\x94'
- 149: 255, # '\x95'
- 150: 255, # '\x96'
- 151: 255, # '\x97'
- 152: 255, # '\x98'
- 153: 255, # '\x99'
- 154: 255, # '\x9a'
- 155: 255, # '\x9b'
- 156: 255, # '\x9c'
- 157: 255, # '\x9d'
- 158: 255, # '\x9e'
- 159: 255, # '\x9f'
- 160: 253, # '\xa0'
- 161: 233, # '‘'
- 162: 90, # '’'
- 163: 253, # '£'
- 164: 253, # '€'
- 165: 253, # '₯'
- 166: 253, # '¦'
- 167: 253, # '§'
- 168: 253, # '¨'
- 169: 253, # '©'
- 170: 253, # 'ͺ'
- 171: 253, # '«'
- 172: 253, # '¬'
- 173: 74, # '\xad'
- 174: 253, # None
- 175: 253, # '―'
- 176: 253, # '°'
- 177: 253, # '±'
- 178: 253, # '²'
- 179: 253, # '³'
- 180: 247, # '΄'
- 181: 248, # '΅'
- 182: 61, # 'Ά'
- 183: 36, # '·'
- 184: 46, # 'Έ'
- 185: 71, # 'Ή'
- 186: 73, # 'Ί'
- 187: 253, # '»'
- 188: 54, # 'Ό'
- 189: 253, # '½'
- 190: 108, # 'Ύ'
- 191: 123, # 'Ώ'
- 192: 110, # 'ΐ'
- 193: 31, # 'Α'
- 194: 51, # 'Β'
- 195: 43, # 'Γ'
- 196: 41, # 'Δ'
- 197: 34, # 'Ε'
- 198: 91, # 'Ζ'
- 199: 40, # 'Η'
- 200: 52, # 'Θ'
- 201: 47, # 'Ι'
- 202: 44, # 'Κ'
- 203: 53, # 'Λ'
- 204: 38, # 'Μ'
- 205: 49, # 'Ν'
- 206: 59, # 'Ξ'
- 207: 39, # 'Ο'
- 208: 35, # 'Π'
- 209: 48, # 'Ρ'
- 210: 250, # None
- 211: 37, # 'Σ'
- 212: 33, # 'Τ'
- 213: 45, # 'Υ'
- 214: 56, # 'Φ'
- 215: 50, # 'Χ'
- 216: 84, # 'Ψ'
- 217: 57, # 'Ω'
- 218: 120, # 'Ϊ'
- 219: 121, # 'Ϋ'
- 220: 17, # 'ά'
- 221: 18, # 'έ'
- 222: 22, # 'ή'
- 223: 15, # 'ί'
- 224: 124, # 'ΰ'
- 225: 1, # 'α'
- 226: 29, # 'β'
- 227: 20, # 'γ'
- 228: 21, # 'δ'
- 229: 3, # 'ε'
- 230: 32, # 'ζ'
- 231: 13, # 'η'
- 232: 25, # 'θ'
- 233: 5, # 'ι'
- 234: 11, # 'κ'
- 235: 16, # 'λ'
- 236: 10, # 'μ'
- 237: 6, # 'ν'
- 238: 30, # 'ξ'
- 239: 4, # 'ο'
- 240: 9, # 'π'
- 241: 8, # 'ρ'
- 242: 14, # 'ς'
- 243: 7, # 'σ'
- 244: 2, # 'τ'
- 245: 12, # 'υ'
- 246: 28, # 'φ'
- 247: 23, # 'χ'
- 248: 42, # 'ψ'
- 249: 24, # 'ω'
- 250: 64, # 'ϊ'
- 251: 75, # 'ϋ'
- 252: 19, # 'ό'
- 253: 26, # 'ύ'
- 254: 27, # 'ώ'
- 255: 253, # None
+ 0: 255, # '\x00'
+ 1: 255, # '\x01'
+ 2: 255, # '\x02'
+ 3: 255, # '\x03'
+ 4: 255, # '\x04'
+ 5: 255, # '\x05'
+ 6: 255, # '\x06'
+ 7: 255, # '\x07'
+ 8: 255, # '\x08'
+ 9: 255, # '\t'
+ 10: 254, # '\n'
+ 11: 255, # '\x0b'
+ 12: 255, # '\x0c'
+ 13: 254, # '\r'
+ 14: 255, # '\x0e'
+ 15: 255, # '\x0f'
+ 16: 255, # '\x10'
+ 17: 255, # '\x11'
+ 18: 255, # '\x12'
+ 19: 255, # '\x13'
+ 20: 255, # '\x14'
+ 21: 255, # '\x15'
+ 22: 255, # '\x16'
+ 23: 255, # '\x17'
+ 24: 255, # '\x18'
+ 25: 255, # '\x19'
+ 26: 255, # '\x1a'
+ 27: 255, # '\x1b'
+ 28: 255, # '\x1c'
+ 29: 255, # '\x1d'
+ 30: 255, # '\x1e'
+ 31: 255, # '\x1f'
+ 32: 253, # ' '
+ 33: 253, # '!'
+ 34: 253, # '"'
+ 35: 253, # '#'
+ 36: 253, # '$'
+ 37: 253, # '%'
+ 38: 253, # '&'
+ 39: 253, # "'"
+ 40: 253, # '('
+ 41: 253, # ')'
+ 42: 253, # '*'
+ 43: 253, # '+'
+ 44: 253, # ','
+ 45: 253, # '-'
+ 46: 253, # '.'
+ 47: 253, # '/'
+ 48: 252, # '0'
+ 49: 252, # '1'
+ 50: 252, # '2'
+ 51: 252, # '3'
+ 52: 252, # '4'
+ 53: 252, # '5'
+ 54: 252, # '6'
+ 55: 252, # '7'
+ 56: 252, # '8'
+ 57: 252, # '9'
+ 58: 253, # ':'
+ 59: 253, # ';'
+ 60: 253, # '<'
+ 61: 253, # '='
+ 62: 253, # '>'
+ 63: 253, # '?'
+ 64: 253, # '@'
+ 65: 82, # 'A'
+ 66: 100, # 'B'
+ 67: 104, # 'C'
+ 68: 94, # 'D'
+ 69: 98, # 'E'
+ 70: 101, # 'F'
+ 71: 116, # 'G'
+ 72: 102, # 'H'
+ 73: 111, # 'I'
+ 74: 187, # 'J'
+ 75: 117, # 'K'
+ 76: 92, # 'L'
+ 77: 88, # 'M'
+ 78: 113, # 'N'
+ 79: 85, # 'O'
+ 80: 79, # 'P'
+ 81: 118, # 'Q'
+ 82: 105, # 'R'
+ 83: 83, # 'S'
+ 84: 67, # 'T'
+ 85: 114, # 'U'
+ 86: 119, # 'V'
+ 87: 95, # 'W'
+ 88: 99, # 'X'
+ 89: 109, # 'Y'
+ 90: 188, # 'Z'
+ 91: 253, # '['
+ 92: 253, # '\\'
+ 93: 253, # ']'
+ 94: 253, # '^'
+ 95: 253, # '_'
+ 96: 253, # '`'
+ 97: 72, # 'a'
+ 98: 70, # 'b'
+ 99: 80, # 'c'
+ 100: 81, # 'd'
+ 101: 60, # 'e'
+ 102: 96, # 'f'
+ 103: 93, # 'g'
+ 104: 89, # 'h'
+ 105: 68, # 'i'
+ 106: 120, # 'j'
+ 107: 97, # 'k'
+ 108: 77, # 'l'
+ 109: 86, # 'm'
+ 110: 69, # 'n'
+ 111: 55, # 'o'
+ 112: 78, # 'p'
+ 113: 115, # 'q'
+ 114: 65, # 'r'
+ 115: 66, # 's'
+ 116: 58, # 't'
+ 117: 76, # 'u'
+ 118: 106, # 'v'
+ 119: 103, # 'w'
+ 120: 87, # 'x'
+ 121: 107, # 'y'
+ 122: 112, # 'z'
+ 123: 253, # '{'
+ 124: 253, # '|'
+ 125: 253, # '}'
+ 126: 253, # '~'
+ 127: 253, # '\x7f'
+ 128: 255, # '\x80'
+ 129: 255, # '\x81'
+ 130: 255, # '\x82'
+ 131: 255, # '\x83'
+ 132: 255, # '\x84'
+ 133: 255, # '\x85'
+ 134: 255, # '\x86'
+ 135: 255, # '\x87'
+ 136: 255, # '\x88'
+ 137: 255, # '\x89'
+ 138: 255, # '\x8a'
+ 139: 255, # '\x8b'
+ 140: 255, # '\x8c'
+ 141: 255, # '\x8d'
+ 142: 255, # '\x8e'
+ 143: 255, # '\x8f'
+ 144: 255, # '\x90'
+ 145: 255, # '\x91'
+ 146: 255, # '\x92'
+ 147: 255, # '\x93'
+ 148: 255, # '\x94'
+ 149: 255, # '\x95'
+ 150: 255, # '\x96'
+ 151: 255, # '\x97'
+ 152: 255, # '\x98'
+ 153: 255, # '\x99'
+ 154: 255, # '\x9a'
+ 155: 255, # '\x9b'
+ 156: 255, # '\x9c'
+ 157: 255, # '\x9d'
+ 158: 255, # '\x9e'
+ 159: 255, # '\x9f'
+ 160: 253, # '\xa0'
+ 161: 233, # '‘'
+ 162: 90, # '’'
+ 163: 253, # '£'
+ 164: 253, # '€'
+ 165: 253, # '₯'
+ 166: 253, # '¦'
+ 167: 253, # '§'
+ 168: 253, # '¨'
+ 169: 253, # '©'
+ 170: 253, # 'ͺ'
+ 171: 253, # '«'
+ 172: 253, # '¬'
+ 173: 74, # '\xad'
+ 174: 253, # None
+ 175: 253, # '―'
+ 176: 253, # '°'
+ 177: 253, # '±'
+ 178: 253, # '²'
+ 179: 253, # '³'
+ 180: 247, # '΄'
+ 181: 248, # '΅'
+ 182: 61, # 'Ά'
+ 183: 36, # '·'
+ 184: 46, # 'Έ'
+ 185: 71, # 'Ή'
+ 186: 73, # 'Ί'
+ 187: 253, # '»'
+ 188: 54, # 'Ό'
+ 189: 253, # '½'
+ 190: 108, # 'Ύ'
+ 191: 123, # 'Ώ'
+ 192: 110, # 'ΐ'
+ 193: 31, # 'Α'
+ 194: 51, # 'Β'
+ 195: 43, # 'Γ'
+ 196: 41, # 'Δ'
+ 197: 34, # 'Ε'
+ 198: 91, # 'Ζ'
+ 199: 40, # 'Η'
+ 200: 52, # 'Θ'
+ 201: 47, # 'Ι'
+ 202: 44, # 'Κ'
+ 203: 53, # 'Λ'
+ 204: 38, # 'Μ'
+ 205: 49, # 'Ν'
+ 206: 59, # 'Ξ'
+ 207: 39, # 'Ο'
+ 208: 35, # 'Π'
+ 209: 48, # 'Ρ'
+ 210: 250, # None
+ 211: 37, # 'Σ'
+ 212: 33, # 'Τ'
+ 213: 45, # 'Υ'
+ 214: 56, # 'Φ'
+ 215: 50, # 'Χ'
+ 216: 84, # 'Ψ'
+ 217: 57, # 'Ω'
+ 218: 120, # 'Ϊ'
+ 219: 121, # 'Ϋ'
+ 220: 17, # 'ά'
+ 221: 18, # 'έ'
+ 222: 22, # 'ή'
+ 223: 15, # 'ί'
+ 224: 124, # 'ΰ'
+ 225: 1, # 'α'
+ 226: 29, # 'β'
+ 227: 20, # 'γ'
+ 228: 21, # 'δ'
+ 229: 3, # 'ε'
+ 230: 32, # 'ζ'
+ 231: 13, # 'η'
+ 232: 25, # 'θ'
+ 233: 5, # 'ι'
+ 234: 11, # 'κ'
+ 235: 16, # 'λ'
+ 236: 10, # 'μ'
+ 237: 6, # 'ν'
+ 238: 30, # 'ξ'
+ 239: 4, # 'ο'
+ 240: 9, # 'π'
+ 241: 8, # 'ρ'
+ 242: 14, # 'ς'
+ 243: 7, # 'σ'
+ 244: 2, # 'τ'
+ 245: 12, # 'υ'
+ 246: 28, # 'φ'
+ 247: 23, # 'χ'
+ 248: 42, # 'ψ'
+ 249: 24, # 'ω'
+ 250: 64, # 'ϊ'
+ 251: 75, # 'ϋ'
+ 252: 19, # 'ό'
+ 253: 26, # 'ύ'
+ 254: 27, # 'ώ'
+ 255: 253, # None
}
-ISO_8859_7_GREEK_MODEL = SingleByteCharSetModel(charset_name='ISO-8859-7',
- language='Greek',
- char_to_order_map=ISO_8859_7_GREEK_CHAR_TO_ORDER,
- language_model=GREEK_LANG_MODEL,
- typical_positive_ratio=0.982851,
- keep_ascii_letters=False,
- alphabet='ΆΈΉΊΌΎΏΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩάέήίαβγδεζηθικλμνξοπρςστυφχψωόύώ')
-
+ISO_8859_7_GREEK_MODEL = SingleByteCharSetModel(
+ charset_name="ISO-8859-7",
+ language="Greek",
+ char_to_order_map=ISO_8859_7_GREEK_CHAR_TO_ORDER,
+ language_model=GREEK_LANG_MODEL,
+ typical_positive_ratio=0.982851,
+ keep_ascii_letters=False,
+ alphabet="ΆΈΉΊΌΎΏΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩάέήίαβγδεζηθικλμνξοπρςστυφχψωόύώ",
+)
diff --git a/src/pip/_vendor/chardet/langhebrewmodel.py b/src/pip/_vendor/chardet/langhebrewmodel.py
index 484c652a4..56d297587 100644
--- a/src/pip/_vendor/chardet/langhebrewmodel.py
+++ b/src/pip/_vendor/chardet/langhebrewmodel.py
@@ -1,9 +1,5 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
from pip._vendor.chardet.sbcharsetprober import SingleByteCharSetModel
-
# 3: Positive
# 2: Likely
# 1: Unlikely
@@ -4115,269 +4111,270 @@ HEBREW_LANG_MODEL = {
# Character Mapping Table(s):
WINDOWS_1255_HEBREW_CHAR_TO_ORDER = {
- 0: 255, # '\x00'
- 1: 255, # '\x01'
- 2: 255, # '\x02'
- 3: 255, # '\x03'
- 4: 255, # '\x04'
- 5: 255, # '\x05'
- 6: 255, # '\x06'
- 7: 255, # '\x07'
- 8: 255, # '\x08'
- 9: 255, # '\t'
- 10: 254, # '\n'
- 11: 255, # '\x0b'
- 12: 255, # '\x0c'
- 13: 254, # '\r'
- 14: 255, # '\x0e'
- 15: 255, # '\x0f'
- 16: 255, # '\x10'
- 17: 255, # '\x11'
- 18: 255, # '\x12'
- 19: 255, # '\x13'
- 20: 255, # '\x14'
- 21: 255, # '\x15'
- 22: 255, # '\x16'
- 23: 255, # '\x17'
- 24: 255, # '\x18'
- 25: 255, # '\x19'
- 26: 255, # '\x1a'
- 27: 255, # '\x1b'
- 28: 255, # '\x1c'
- 29: 255, # '\x1d'
- 30: 255, # '\x1e'
- 31: 255, # '\x1f'
- 32: 253, # ' '
- 33: 253, # '!'
- 34: 253, # '"'
- 35: 253, # '#'
- 36: 253, # '$'
- 37: 253, # '%'
- 38: 253, # '&'
- 39: 253, # "'"
- 40: 253, # '('
- 41: 253, # ')'
- 42: 253, # '*'
- 43: 253, # '+'
- 44: 253, # ','
- 45: 253, # '-'
- 46: 253, # '.'
- 47: 253, # '/'
- 48: 252, # '0'
- 49: 252, # '1'
- 50: 252, # '2'
- 51: 252, # '3'
- 52: 252, # '4'
- 53: 252, # '5'
- 54: 252, # '6'
- 55: 252, # '7'
- 56: 252, # '8'
- 57: 252, # '9'
- 58: 253, # ':'
- 59: 253, # ';'
- 60: 253, # '<'
- 61: 253, # '='
- 62: 253, # '>'
- 63: 253, # '?'
- 64: 253, # '@'
- 65: 69, # 'A'
- 66: 91, # 'B'
- 67: 79, # 'C'
- 68: 80, # 'D'
- 69: 92, # 'E'
- 70: 89, # 'F'
- 71: 97, # 'G'
- 72: 90, # 'H'
- 73: 68, # 'I'
- 74: 111, # 'J'
- 75: 112, # 'K'
- 76: 82, # 'L'
- 77: 73, # 'M'
- 78: 95, # 'N'
- 79: 85, # 'O'
- 80: 78, # 'P'
- 81: 121, # 'Q'
- 82: 86, # 'R'
- 83: 71, # 'S'
- 84: 67, # 'T'
- 85: 102, # 'U'
- 86: 107, # 'V'
- 87: 84, # 'W'
- 88: 114, # 'X'
- 89: 103, # 'Y'
- 90: 115, # 'Z'
- 91: 253, # '['
- 92: 253, # '\\'
- 93: 253, # ']'
- 94: 253, # '^'
- 95: 253, # '_'
- 96: 253, # '`'
- 97: 50, # 'a'
- 98: 74, # 'b'
- 99: 60, # 'c'
- 100: 61, # 'd'
- 101: 42, # 'e'
- 102: 76, # 'f'
- 103: 70, # 'g'
- 104: 64, # 'h'
- 105: 53, # 'i'
- 106: 105, # 'j'
- 107: 93, # 'k'
- 108: 56, # 'l'
- 109: 65, # 'm'
- 110: 54, # 'n'
- 111: 49, # 'o'
- 112: 66, # 'p'
- 113: 110, # 'q'
- 114: 51, # 'r'
- 115: 43, # 's'
- 116: 44, # 't'
- 117: 63, # 'u'
- 118: 81, # 'v'
- 119: 77, # 'w'
- 120: 98, # 'x'
- 121: 75, # 'y'
- 122: 108, # 'z'
- 123: 253, # '{'
- 124: 253, # '|'
- 125: 253, # '}'
- 126: 253, # '~'
- 127: 253, # '\x7f'
- 128: 124, # '€'
- 129: 202, # None
- 130: 203, # '‚'
- 131: 204, # 'ƒ'
- 132: 205, # '„'
- 133: 40, # '…'
- 134: 58, # '†'
- 135: 206, # '‡'
- 136: 207, # 'ˆ'
- 137: 208, # '‰'
- 138: 209, # None
- 139: 210, # '‹'
- 140: 211, # None
- 141: 212, # None
- 142: 213, # None
- 143: 214, # None
- 144: 215, # None
- 145: 83, # '‘'
- 146: 52, # '’'
- 147: 47, # '“'
- 148: 46, # '”'
- 149: 72, # '•'
- 150: 32, # '–'
- 151: 94, # '—'
- 152: 216, # '˜'
- 153: 113, # '™'
- 154: 217, # None
- 155: 109, # '›'
- 156: 218, # None
- 157: 219, # None
- 158: 220, # None
- 159: 221, # None
- 160: 34, # '\xa0'
- 161: 116, # '¡'
- 162: 222, # '¢'
- 163: 118, # '£'
- 164: 100, # '₪'
- 165: 223, # '¥'
- 166: 224, # '¦'
- 167: 117, # '§'
- 168: 119, # '¨'
- 169: 104, # '©'
- 170: 125, # '×'
- 171: 225, # '«'
- 172: 226, # '¬'
- 173: 87, # '\xad'
- 174: 99, # '®'
- 175: 227, # '¯'
- 176: 106, # '°'
- 177: 122, # '±'
- 178: 123, # '²'
- 179: 228, # '³'
- 180: 55, # '´'
- 181: 229, # 'µ'
- 182: 230, # '¶'
- 183: 101, # '·'
- 184: 231, # '¸'
- 185: 232, # '¹'
- 186: 120, # '÷'
- 187: 233, # '»'
- 188: 48, # '¼'
- 189: 39, # '½'
- 190: 57, # '¾'
- 191: 234, # '¿'
- 192: 30, # 'ְ'
- 193: 59, # 'ֱ'
- 194: 41, # 'ֲ'
- 195: 88, # 'ֳ'
- 196: 33, # 'ִ'
- 197: 37, # 'ֵ'
- 198: 36, # 'ֶ'
- 199: 31, # 'ַ'
- 200: 29, # 'ָ'
- 201: 35, # 'ֹ'
- 202: 235, # None
- 203: 62, # 'ֻ'
- 204: 28, # 'ּ'
- 205: 236, # 'ֽ'
- 206: 126, # '־'
- 207: 237, # 'ֿ'
- 208: 238, # '׀'
- 209: 38, # 'ׁ'
- 210: 45, # 'ׂ'
- 211: 239, # '׃'
- 212: 240, # 'װ'
- 213: 241, # 'ױ'
- 214: 242, # 'ײ'
- 215: 243, # '׳'
- 216: 127, # '״'
- 217: 244, # None
- 218: 245, # None
- 219: 246, # None
- 220: 247, # None
- 221: 248, # None
- 222: 249, # None
- 223: 250, # None
- 224: 9, # 'א'
- 225: 8, # 'ב'
- 226: 20, # 'ג'
- 227: 16, # 'ד'
- 228: 3, # 'ה'
- 229: 2, # 'ו'
- 230: 24, # 'ז'
- 231: 14, # 'ח'
- 232: 22, # 'ט'
- 233: 1, # 'י'
- 234: 25, # 'ך'
- 235: 15, # 'כ'
- 236: 4, # 'ל'
- 237: 11, # 'ם'
- 238: 6, # 'מ'
- 239: 23, # 'ן'
- 240: 12, # 'נ'
- 241: 19, # 'ס'
- 242: 13, # 'ע'
- 243: 26, # 'ף'
- 244: 18, # 'פ'
- 245: 27, # 'ץ'
- 246: 21, # 'צ'
- 247: 17, # 'ק'
- 248: 7, # 'ר'
- 249: 10, # 'ש'
- 250: 5, # 'ת'
- 251: 251, # None
- 252: 252, # None
- 253: 128, # '\u200e'
- 254: 96, # '\u200f'
- 255: 253, # None
+ 0: 255, # '\x00'
+ 1: 255, # '\x01'
+ 2: 255, # '\x02'
+ 3: 255, # '\x03'
+ 4: 255, # '\x04'
+ 5: 255, # '\x05'
+ 6: 255, # '\x06'
+ 7: 255, # '\x07'
+ 8: 255, # '\x08'
+ 9: 255, # '\t'
+ 10: 254, # '\n'
+ 11: 255, # '\x0b'
+ 12: 255, # '\x0c'
+ 13: 254, # '\r'
+ 14: 255, # '\x0e'
+ 15: 255, # '\x0f'
+ 16: 255, # '\x10'
+ 17: 255, # '\x11'
+ 18: 255, # '\x12'
+ 19: 255, # '\x13'
+ 20: 255, # '\x14'
+ 21: 255, # '\x15'
+ 22: 255, # '\x16'
+ 23: 255, # '\x17'
+ 24: 255, # '\x18'
+ 25: 255, # '\x19'
+ 26: 255, # '\x1a'
+ 27: 255, # '\x1b'
+ 28: 255, # '\x1c'
+ 29: 255, # '\x1d'
+ 30: 255, # '\x1e'
+ 31: 255, # '\x1f'
+ 32: 253, # ' '
+ 33: 253, # '!'
+ 34: 253, # '"'
+ 35: 253, # '#'
+ 36: 253, # '$'
+ 37: 253, # '%'
+ 38: 253, # '&'
+ 39: 253, # "'"
+ 40: 253, # '('
+ 41: 253, # ')'
+ 42: 253, # '*'
+ 43: 253, # '+'
+ 44: 253, # ','
+ 45: 253, # '-'
+ 46: 253, # '.'
+ 47: 253, # '/'
+ 48: 252, # '0'
+ 49: 252, # '1'
+ 50: 252, # '2'
+ 51: 252, # '3'
+ 52: 252, # '4'
+ 53: 252, # '5'
+ 54: 252, # '6'
+ 55: 252, # '7'
+ 56: 252, # '8'
+ 57: 252, # '9'
+ 58: 253, # ':'
+ 59: 253, # ';'
+ 60: 253, # '<'
+ 61: 253, # '='
+ 62: 253, # '>'
+ 63: 253, # '?'
+ 64: 253, # '@'
+ 65: 69, # 'A'
+ 66: 91, # 'B'
+ 67: 79, # 'C'
+ 68: 80, # 'D'
+ 69: 92, # 'E'
+ 70: 89, # 'F'
+ 71: 97, # 'G'
+ 72: 90, # 'H'
+ 73: 68, # 'I'
+ 74: 111, # 'J'
+ 75: 112, # 'K'
+ 76: 82, # 'L'
+ 77: 73, # 'M'
+ 78: 95, # 'N'
+ 79: 85, # 'O'
+ 80: 78, # 'P'
+ 81: 121, # 'Q'
+ 82: 86, # 'R'
+ 83: 71, # 'S'
+ 84: 67, # 'T'
+ 85: 102, # 'U'
+ 86: 107, # 'V'
+ 87: 84, # 'W'
+ 88: 114, # 'X'
+ 89: 103, # 'Y'
+ 90: 115, # 'Z'
+ 91: 253, # '['
+ 92: 253, # '\\'
+ 93: 253, # ']'
+ 94: 253, # '^'
+ 95: 253, # '_'
+ 96: 253, # '`'
+ 97: 50, # 'a'
+ 98: 74, # 'b'
+ 99: 60, # 'c'
+ 100: 61, # 'd'
+ 101: 42, # 'e'
+ 102: 76, # 'f'
+ 103: 70, # 'g'
+ 104: 64, # 'h'
+ 105: 53, # 'i'
+ 106: 105, # 'j'
+ 107: 93, # 'k'
+ 108: 56, # 'l'
+ 109: 65, # 'm'
+ 110: 54, # 'n'
+ 111: 49, # 'o'
+ 112: 66, # 'p'
+ 113: 110, # 'q'
+ 114: 51, # 'r'
+ 115: 43, # 's'
+ 116: 44, # 't'
+ 117: 63, # 'u'
+ 118: 81, # 'v'
+ 119: 77, # 'w'
+ 120: 98, # 'x'
+ 121: 75, # 'y'
+ 122: 108, # 'z'
+ 123: 253, # '{'
+ 124: 253, # '|'
+ 125: 253, # '}'
+ 126: 253, # '~'
+ 127: 253, # '\x7f'
+ 128: 124, # '€'
+ 129: 202, # None
+ 130: 203, # '‚'
+ 131: 204, # 'ƒ'
+ 132: 205, # '„'
+ 133: 40, # '…'
+ 134: 58, # '†'
+ 135: 206, # '‡'
+ 136: 207, # 'ˆ'
+ 137: 208, # '‰'
+ 138: 209, # None
+ 139: 210, # '‹'
+ 140: 211, # None
+ 141: 212, # None
+ 142: 213, # None
+ 143: 214, # None
+ 144: 215, # None
+ 145: 83, # '‘'
+ 146: 52, # '’'
+ 147: 47, # '“'
+ 148: 46, # '”'
+ 149: 72, # '•'
+ 150: 32, # '–'
+ 151: 94, # '—'
+ 152: 216, # '˜'
+ 153: 113, # '™'
+ 154: 217, # None
+ 155: 109, # '›'
+ 156: 218, # None
+ 157: 219, # None
+ 158: 220, # None
+ 159: 221, # None
+ 160: 34, # '\xa0'
+ 161: 116, # '¡'
+ 162: 222, # '¢'
+ 163: 118, # '£'
+ 164: 100, # '₪'
+ 165: 223, # '¥'
+ 166: 224, # '¦'
+ 167: 117, # '§'
+ 168: 119, # '¨'
+ 169: 104, # '©'
+ 170: 125, # '×'
+ 171: 225, # '«'
+ 172: 226, # '¬'
+ 173: 87, # '\xad'
+ 174: 99, # '®'
+ 175: 227, # '¯'
+ 176: 106, # '°'
+ 177: 122, # '±'
+ 178: 123, # '²'
+ 179: 228, # '³'
+ 180: 55, # '´'
+ 181: 229, # 'µ'
+ 182: 230, # '¶'
+ 183: 101, # '·'
+ 184: 231, # '¸'
+ 185: 232, # '¹'
+ 186: 120, # '÷'
+ 187: 233, # '»'
+ 188: 48, # '¼'
+ 189: 39, # '½'
+ 190: 57, # '¾'
+ 191: 234, # '¿'
+ 192: 30, # 'ְ'
+ 193: 59, # 'ֱ'
+ 194: 41, # 'ֲ'
+ 195: 88, # 'ֳ'
+ 196: 33, # 'ִ'
+ 197: 37, # 'ֵ'
+ 198: 36, # 'ֶ'
+ 199: 31, # 'ַ'
+ 200: 29, # 'ָ'
+ 201: 35, # 'ֹ'
+ 202: 235, # None
+ 203: 62, # 'ֻ'
+ 204: 28, # 'ּ'
+ 205: 236, # 'ֽ'
+ 206: 126, # '־'
+ 207: 237, # 'ֿ'
+ 208: 238, # '׀'
+ 209: 38, # 'ׁ'
+ 210: 45, # 'ׂ'
+ 211: 239, # '׃'
+ 212: 240, # 'װ'
+ 213: 241, # 'ױ'
+ 214: 242, # 'ײ'
+ 215: 243, # '׳'
+ 216: 127, # '״'
+ 217: 244, # None
+ 218: 245, # None
+ 219: 246, # None
+ 220: 247, # None
+ 221: 248, # None
+ 222: 249, # None
+ 223: 250, # None
+ 224: 9, # 'א'
+ 225: 8, # 'ב'
+ 226: 20, # 'ג'
+ 227: 16, # 'ד'
+ 228: 3, # 'ה'
+ 229: 2, # 'ו'
+ 230: 24, # 'ז'
+ 231: 14, # 'ח'
+ 232: 22, # 'ט'
+ 233: 1, # 'י'
+ 234: 25, # 'ך'
+ 235: 15, # 'כ'
+ 236: 4, # 'ל'
+ 237: 11, # 'ם'
+ 238: 6, # 'מ'
+ 239: 23, # 'ן'
+ 240: 12, # 'נ'
+ 241: 19, # 'ס'
+ 242: 13, # 'ע'
+ 243: 26, # 'ף'
+ 244: 18, # 'פ'
+ 245: 27, # 'ץ'
+ 246: 21, # 'צ'
+ 247: 17, # 'ק'
+ 248: 7, # 'ר'
+ 249: 10, # 'ש'
+ 250: 5, # 'ת'
+ 251: 251, # None
+ 252: 252, # None
+ 253: 128, # '\u200e'
+ 254: 96, # '\u200f'
+ 255: 253, # None
}
-WINDOWS_1255_HEBREW_MODEL = SingleByteCharSetModel(charset_name='windows-1255',
- language='Hebrew',
- char_to_order_map=WINDOWS_1255_HEBREW_CHAR_TO_ORDER,
- language_model=HEBREW_LANG_MODEL,
- typical_positive_ratio=0.984004,
- keep_ascii_letters=False,
- alphabet='אבגדהוזחטיךכלםמןנסעףפץצקרשתװױײ')
-
+WINDOWS_1255_HEBREW_MODEL = SingleByteCharSetModel(
+ charset_name="windows-1255",
+ language="Hebrew",
+ char_to_order_map=WINDOWS_1255_HEBREW_CHAR_TO_ORDER,
+ language_model=HEBREW_LANG_MODEL,
+ typical_positive_ratio=0.984004,
+ keep_ascii_letters=False,
+ alphabet="אבגדהוזחטיךכלםמןנסעףפץצקרשתװױײ",
+)
diff --git a/src/pip/_vendor/chardet/langhungarianmodel.py b/src/pip/_vendor/chardet/langhungarianmodel.py
index bbc5cda64..09a0d326b 100644
--- a/src/pip/_vendor/chardet/langhungarianmodel.py
+++ b/src/pip/_vendor/chardet/langhungarianmodel.py
@@ -1,9 +1,5 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
from pip._vendor.chardet.sbcharsetprober import SingleByteCharSetModel
-
# 3: Positive
# 2: Likely
# 1: Unlikely
@@ -4115,536 +4111,539 @@ HUNGARIAN_LANG_MODEL = {
# Character Mapping Table(s):
WINDOWS_1250_HUNGARIAN_CHAR_TO_ORDER = {
- 0: 255, # '\x00'
- 1: 255, # '\x01'
- 2: 255, # '\x02'
- 3: 255, # '\x03'
- 4: 255, # '\x04'
- 5: 255, # '\x05'
- 6: 255, # '\x06'
- 7: 255, # '\x07'
- 8: 255, # '\x08'
- 9: 255, # '\t'
- 10: 254, # '\n'
- 11: 255, # '\x0b'
- 12: 255, # '\x0c'
- 13: 254, # '\r'
- 14: 255, # '\x0e'
- 15: 255, # '\x0f'
- 16: 255, # '\x10'
- 17: 255, # '\x11'
- 18: 255, # '\x12'
- 19: 255, # '\x13'
- 20: 255, # '\x14'
- 21: 255, # '\x15'
- 22: 255, # '\x16'
- 23: 255, # '\x17'
- 24: 255, # '\x18'
- 25: 255, # '\x19'
- 26: 255, # '\x1a'
- 27: 255, # '\x1b'
- 28: 255, # '\x1c'
- 29: 255, # '\x1d'
- 30: 255, # '\x1e'
- 31: 255, # '\x1f'
- 32: 253, # ' '
- 33: 253, # '!'
- 34: 253, # '"'
- 35: 253, # '#'
- 36: 253, # '$'
- 37: 253, # '%'
- 38: 253, # '&'
- 39: 253, # "'"
- 40: 253, # '('
- 41: 253, # ')'
- 42: 253, # '*'
- 43: 253, # '+'
- 44: 253, # ','
- 45: 253, # '-'
- 46: 253, # '.'
- 47: 253, # '/'
- 48: 252, # '0'
- 49: 252, # '1'
- 50: 252, # '2'
- 51: 252, # '3'
- 52: 252, # '4'
- 53: 252, # '5'
- 54: 252, # '6'
- 55: 252, # '7'
- 56: 252, # '8'
- 57: 252, # '9'
- 58: 253, # ':'
- 59: 253, # ';'
- 60: 253, # '<'
- 61: 253, # '='
- 62: 253, # '>'
- 63: 253, # '?'
- 64: 253, # '@'
- 65: 28, # 'A'
- 66: 40, # 'B'
- 67: 54, # 'C'
- 68: 45, # 'D'
- 69: 32, # 'E'
- 70: 50, # 'F'
- 71: 49, # 'G'
- 72: 38, # 'H'
- 73: 39, # 'I'
- 74: 53, # 'J'
- 75: 36, # 'K'
- 76: 41, # 'L'
- 77: 34, # 'M'
- 78: 35, # 'N'
- 79: 47, # 'O'
- 80: 46, # 'P'
- 81: 72, # 'Q'
- 82: 43, # 'R'
- 83: 33, # 'S'
- 84: 37, # 'T'
- 85: 57, # 'U'
- 86: 48, # 'V'
- 87: 64, # 'W'
- 88: 68, # 'X'
- 89: 55, # 'Y'
- 90: 52, # 'Z'
- 91: 253, # '['
- 92: 253, # '\\'
- 93: 253, # ']'
- 94: 253, # '^'
- 95: 253, # '_'
- 96: 253, # '`'
- 97: 2, # 'a'
- 98: 18, # 'b'
- 99: 26, # 'c'
- 100: 17, # 'd'
- 101: 1, # 'e'
- 102: 27, # 'f'
- 103: 12, # 'g'
- 104: 20, # 'h'
- 105: 9, # 'i'
- 106: 22, # 'j'
- 107: 7, # 'k'
- 108: 6, # 'l'
- 109: 13, # 'm'
- 110: 4, # 'n'
- 111: 8, # 'o'
- 112: 23, # 'p'
- 113: 67, # 'q'
- 114: 10, # 'r'
- 115: 5, # 's'
- 116: 3, # 't'
- 117: 21, # 'u'
- 118: 19, # 'v'
- 119: 65, # 'w'
- 120: 62, # 'x'
- 121: 16, # 'y'
- 122: 11, # 'z'
- 123: 253, # '{'
- 124: 253, # '|'
- 125: 253, # '}'
- 126: 253, # '~'
- 127: 253, # '\x7f'
- 128: 161, # '€'
- 129: 162, # None
- 130: 163, # '‚'
- 131: 164, # None
- 132: 165, # '„'
- 133: 166, # '…'
- 134: 167, # '†'
- 135: 168, # '‡'
- 136: 169, # None
- 137: 170, # '‰'
- 138: 171, # 'Š'
- 139: 172, # '‹'
- 140: 173, # 'Ś'
- 141: 174, # 'Ť'
- 142: 175, # 'Ž'
- 143: 176, # 'Ź'
- 144: 177, # None
- 145: 178, # '‘'
- 146: 179, # '’'
- 147: 180, # '“'
- 148: 78, # '”'
- 149: 181, # '•'
- 150: 69, # '–'
- 151: 182, # '—'
- 152: 183, # None
- 153: 184, # '™'
- 154: 185, # 'š'
- 155: 186, # '›'
- 156: 187, # 'ś'
- 157: 188, # 'ť'
- 158: 189, # 'ž'
- 159: 190, # 'ź'
- 160: 191, # '\xa0'
- 161: 192, # 'ˇ'
- 162: 193, # '˘'
- 163: 194, # 'Ł'
- 164: 195, # '¤'
- 165: 196, # 'Ą'
- 166: 197, # '¦'
- 167: 76, # '§'
- 168: 198, # '¨'
- 169: 199, # '©'
- 170: 200, # 'Ş'
- 171: 201, # '«'
- 172: 202, # '¬'
- 173: 203, # '\xad'
- 174: 204, # '®'
- 175: 205, # 'Ż'
- 176: 81, # '°'
- 177: 206, # '±'
- 178: 207, # '˛'
- 179: 208, # 'ł'
- 180: 209, # '´'
- 181: 210, # 'µ'
- 182: 211, # '¶'
- 183: 212, # '·'
- 184: 213, # '¸'
- 185: 214, # 'ą'
- 186: 215, # 'ş'
- 187: 216, # '»'
- 188: 217, # 'Ľ'
- 189: 218, # '˝'
- 190: 219, # 'ľ'
- 191: 220, # 'ż'
- 192: 221, # 'Ŕ'
- 193: 51, # 'Á'
- 194: 83, # 'Â'
- 195: 222, # 'Ă'
- 196: 80, # 'Ä'
- 197: 223, # 'Ĺ'
- 198: 224, # 'Ć'
- 199: 225, # 'Ç'
- 200: 226, # 'Č'
- 201: 44, # 'É'
- 202: 227, # 'Ę'
- 203: 228, # 'Ë'
- 204: 229, # 'Ě'
- 205: 61, # 'Í'
- 206: 230, # 'Î'
- 207: 231, # 'Ď'
- 208: 232, # 'Đ'
- 209: 233, # 'Ń'
- 210: 234, # 'Ň'
- 211: 58, # 'Ó'
- 212: 235, # 'Ô'
- 213: 66, # 'Ő'
- 214: 59, # 'Ö'
- 215: 236, # '×'
- 216: 237, # 'Ř'
- 217: 238, # 'Ů'
- 218: 60, # 'Ú'
- 219: 70, # 'Ű'
- 220: 63, # 'Ü'
- 221: 239, # 'Ý'
- 222: 240, # 'Ţ'
- 223: 241, # 'ß'
- 224: 84, # 'ŕ'
- 225: 14, # 'á'
- 226: 75, # 'â'
- 227: 242, # 'ă'
- 228: 71, # 'ä'
- 229: 82, # 'ĺ'
- 230: 243, # 'ć'
- 231: 73, # 'ç'
- 232: 244, # 'č'
- 233: 15, # 'é'
- 234: 85, # 'ę'
- 235: 79, # 'ë'
- 236: 86, # 'ě'
- 237: 30, # 'í'
- 238: 77, # 'î'
- 239: 87, # 'ď'
- 240: 245, # 'đ'
- 241: 246, # 'ń'
- 242: 247, # 'ň'
- 243: 25, # 'ó'
- 244: 74, # 'ô'
- 245: 42, # 'ő'
- 246: 24, # 'ö'
- 247: 248, # '÷'
- 248: 249, # 'ř'
- 249: 250, # 'ů'
- 250: 31, # 'ú'
- 251: 56, # 'ű'
- 252: 29, # 'ü'
- 253: 251, # 'ý'
- 254: 252, # 'ţ'
- 255: 253, # '˙'
+ 0: 255, # '\x00'
+ 1: 255, # '\x01'
+ 2: 255, # '\x02'
+ 3: 255, # '\x03'
+ 4: 255, # '\x04'
+ 5: 255, # '\x05'
+ 6: 255, # '\x06'
+ 7: 255, # '\x07'
+ 8: 255, # '\x08'
+ 9: 255, # '\t'
+ 10: 254, # '\n'
+ 11: 255, # '\x0b'
+ 12: 255, # '\x0c'
+ 13: 254, # '\r'
+ 14: 255, # '\x0e'
+ 15: 255, # '\x0f'
+ 16: 255, # '\x10'
+ 17: 255, # '\x11'
+ 18: 255, # '\x12'
+ 19: 255, # '\x13'
+ 20: 255, # '\x14'
+ 21: 255, # '\x15'
+ 22: 255, # '\x16'
+ 23: 255, # '\x17'
+ 24: 255, # '\x18'
+ 25: 255, # '\x19'
+ 26: 255, # '\x1a'
+ 27: 255, # '\x1b'
+ 28: 255, # '\x1c'
+ 29: 255, # '\x1d'
+ 30: 255, # '\x1e'
+ 31: 255, # '\x1f'
+ 32: 253, # ' '
+ 33: 253, # '!'
+ 34: 253, # '"'
+ 35: 253, # '#'
+ 36: 253, # '$'
+ 37: 253, # '%'
+ 38: 253, # '&'
+ 39: 253, # "'"
+ 40: 253, # '('
+ 41: 253, # ')'
+ 42: 253, # '*'
+ 43: 253, # '+'
+ 44: 253, # ','
+ 45: 253, # '-'
+ 46: 253, # '.'
+ 47: 253, # '/'
+ 48: 252, # '0'
+ 49: 252, # '1'
+ 50: 252, # '2'
+ 51: 252, # '3'
+ 52: 252, # '4'
+ 53: 252, # '5'
+ 54: 252, # '6'
+ 55: 252, # '7'
+ 56: 252, # '8'
+ 57: 252, # '9'
+ 58: 253, # ':'
+ 59: 253, # ';'
+ 60: 253, # '<'
+ 61: 253, # '='
+ 62: 253, # '>'
+ 63: 253, # '?'
+ 64: 253, # '@'
+ 65: 28, # 'A'
+ 66: 40, # 'B'
+ 67: 54, # 'C'
+ 68: 45, # 'D'
+ 69: 32, # 'E'
+ 70: 50, # 'F'
+ 71: 49, # 'G'
+ 72: 38, # 'H'
+ 73: 39, # 'I'
+ 74: 53, # 'J'
+ 75: 36, # 'K'
+ 76: 41, # 'L'
+ 77: 34, # 'M'
+ 78: 35, # 'N'
+ 79: 47, # 'O'
+ 80: 46, # 'P'
+ 81: 72, # 'Q'
+ 82: 43, # 'R'
+ 83: 33, # 'S'
+ 84: 37, # 'T'
+ 85: 57, # 'U'
+ 86: 48, # 'V'
+ 87: 64, # 'W'
+ 88: 68, # 'X'
+ 89: 55, # 'Y'
+ 90: 52, # 'Z'
+ 91: 253, # '['
+ 92: 253, # '\\'
+ 93: 253, # ']'
+ 94: 253, # '^'
+ 95: 253, # '_'
+ 96: 253, # '`'
+ 97: 2, # 'a'
+ 98: 18, # 'b'
+ 99: 26, # 'c'
+ 100: 17, # 'd'
+ 101: 1, # 'e'
+ 102: 27, # 'f'
+ 103: 12, # 'g'
+ 104: 20, # 'h'
+ 105: 9, # 'i'
+ 106: 22, # 'j'
+ 107: 7, # 'k'
+ 108: 6, # 'l'
+ 109: 13, # 'm'
+ 110: 4, # 'n'
+ 111: 8, # 'o'
+ 112: 23, # 'p'
+ 113: 67, # 'q'
+ 114: 10, # 'r'
+ 115: 5, # 's'
+ 116: 3, # 't'
+ 117: 21, # 'u'
+ 118: 19, # 'v'
+ 119: 65, # 'w'
+ 120: 62, # 'x'
+ 121: 16, # 'y'
+ 122: 11, # 'z'
+ 123: 253, # '{'
+ 124: 253, # '|'
+ 125: 253, # '}'
+ 126: 253, # '~'
+ 127: 253, # '\x7f'
+ 128: 161, # '€'
+ 129: 162, # None
+ 130: 163, # '‚'
+ 131: 164, # None
+ 132: 165, # '„'
+ 133: 166, # '…'
+ 134: 167, # '†'
+ 135: 168, # '‡'
+ 136: 169, # None
+ 137: 170, # '‰'
+ 138: 171, # 'Š'
+ 139: 172, # '‹'
+ 140: 173, # 'Ś'
+ 141: 174, # 'Ť'
+ 142: 175, # 'Ž'
+ 143: 176, # 'Ź'
+ 144: 177, # None
+ 145: 178, # '‘'
+ 146: 179, # '’'
+ 147: 180, # '“'
+ 148: 78, # '”'
+ 149: 181, # '•'
+ 150: 69, # '–'
+ 151: 182, # '—'
+ 152: 183, # None
+ 153: 184, # '™'
+ 154: 185, # 'š'
+ 155: 186, # '›'
+ 156: 187, # 'ś'
+ 157: 188, # 'ť'
+ 158: 189, # 'ž'
+ 159: 190, # 'ź'
+ 160: 191, # '\xa0'
+ 161: 192, # 'ˇ'
+ 162: 193, # '˘'
+ 163: 194, # 'Ł'
+ 164: 195, # '¤'
+ 165: 196, # 'Ą'
+ 166: 197, # '¦'
+ 167: 76, # '§'
+ 168: 198, # '¨'
+ 169: 199, # '©'
+ 170: 200, # 'Ş'
+ 171: 201, # '«'
+ 172: 202, # '¬'
+ 173: 203, # '\xad'
+ 174: 204, # '®'
+ 175: 205, # 'Ż'
+ 176: 81, # '°'
+ 177: 206, # '±'
+ 178: 207, # '˛'
+ 179: 208, # 'ł'
+ 180: 209, # '´'
+ 181: 210, # 'µ'
+ 182: 211, # '¶'
+ 183: 212, # '·'
+ 184: 213, # '¸'
+ 185: 214, # 'ą'
+ 186: 215, # 'ş'
+ 187: 216, # '»'
+ 188: 217, # 'Ľ'
+ 189: 218, # '˝'
+ 190: 219, # 'ľ'
+ 191: 220, # 'ż'
+ 192: 221, # 'Ŕ'
+ 193: 51, # 'Á'
+ 194: 83, # 'Â'
+ 195: 222, # 'Ă'
+ 196: 80, # 'Ä'
+ 197: 223, # 'Ĺ'
+ 198: 224, # 'Ć'
+ 199: 225, # 'Ç'
+ 200: 226, # 'Č'
+ 201: 44, # 'É'
+ 202: 227, # 'Ę'
+ 203: 228, # 'Ë'
+ 204: 229, # 'Ě'
+ 205: 61, # 'Í'
+ 206: 230, # 'Î'
+ 207: 231, # 'Ď'
+ 208: 232, # 'Đ'
+ 209: 233, # 'Ń'
+ 210: 234, # 'Ň'
+ 211: 58, # 'Ó'
+ 212: 235, # 'Ô'
+ 213: 66, # 'Ő'
+ 214: 59, # 'Ö'
+ 215: 236, # '×'
+ 216: 237, # 'Ř'
+ 217: 238, # 'Ů'
+ 218: 60, # 'Ú'
+ 219: 70, # 'Ű'
+ 220: 63, # 'Ü'
+ 221: 239, # 'Ý'
+ 222: 240, # 'Ţ'
+ 223: 241, # 'ß'
+ 224: 84, # 'ŕ'
+ 225: 14, # 'á'
+ 226: 75, # 'â'
+ 227: 242, # 'ă'
+ 228: 71, # 'ä'
+ 229: 82, # 'ĺ'
+ 230: 243, # 'ć'
+ 231: 73, # 'ç'
+ 232: 244, # 'č'
+ 233: 15, # 'é'
+ 234: 85, # 'ę'
+ 235: 79, # 'ë'
+ 236: 86, # 'ě'
+ 237: 30, # 'í'
+ 238: 77, # 'î'
+ 239: 87, # 'ď'
+ 240: 245, # 'đ'
+ 241: 246, # 'ń'
+ 242: 247, # 'ň'
+ 243: 25, # 'ó'
+ 244: 74, # 'ô'
+ 245: 42, # 'ő'
+ 246: 24, # 'ö'
+ 247: 248, # '÷'
+ 248: 249, # 'ř'
+ 249: 250, # 'ů'
+ 250: 31, # 'ú'
+ 251: 56, # 'ű'
+ 252: 29, # 'ü'
+ 253: 251, # 'ý'
+ 254: 252, # 'ţ'
+ 255: 253, # '˙'
}
-WINDOWS_1250_HUNGARIAN_MODEL = SingleByteCharSetModel(charset_name='windows-1250',
- language='Hungarian',
- char_to_order_map=WINDOWS_1250_HUNGARIAN_CHAR_TO_ORDER,
- language_model=HUNGARIAN_LANG_MODEL,
- typical_positive_ratio=0.947368,
- keep_ascii_letters=True,
- alphabet='ABCDEFGHIJKLMNOPRSTUVZabcdefghijklmnoprstuvzÁÉÍÓÖÚÜáéíóöúüŐőŰű')
+WINDOWS_1250_HUNGARIAN_MODEL = SingleByteCharSetModel(
+ charset_name="windows-1250",
+ language="Hungarian",
+ char_to_order_map=WINDOWS_1250_HUNGARIAN_CHAR_TO_ORDER,
+ language_model=HUNGARIAN_LANG_MODEL,
+ typical_positive_ratio=0.947368,
+ keep_ascii_letters=True,
+ alphabet="ABCDEFGHIJKLMNOPRSTUVZabcdefghijklmnoprstuvzÁÉÍÓÖÚÜáéíóöúüŐőŰű",
+)
ISO_8859_2_HUNGARIAN_CHAR_TO_ORDER = {
- 0: 255, # '\x00'
- 1: 255, # '\x01'
- 2: 255, # '\x02'
- 3: 255, # '\x03'
- 4: 255, # '\x04'
- 5: 255, # '\x05'
- 6: 255, # '\x06'
- 7: 255, # '\x07'
- 8: 255, # '\x08'
- 9: 255, # '\t'
- 10: 254, # '\n'
- 11: 255, # '\x0b'
- 12: 255, # '\x0c'
- 13: 254, # '\r'
- 14: 255, # '\x0e'
- 15: 255, # '\x0f'
- 16: 255, # '\x10'
- 17: 255, # '\x11'
- 18: 255, # '\x12'
- 19: 255, # '\x13'
- 20: 255, # '\x14'
- 21: 255, # '\x15'
- 22: 255, # '\x16'
- 23: 255, # '\x17'
- 24: 255, # '\x18'
- 25: 255, # '\x19'
- 26: 255, # '\x1a'
- 27: 255, # '\x1b'
- 28: 255, # '\x1c'
- 29: 255, # '\x1d'
- 30: 255, # '\x1e'
- 31: 255, # '\x1f'
- 32: 253, # ' '
- 33: 253, # '!'
- 34: 253, # '"'
- 35: 253, # '#'
- 36: 253, # '$'
- 37: 253, # '%'
- 38: 253, # '&'
- 39: 253, # "'"
- 40: 253, # '('
- 41: 253, # ')'
- 42: 253, # '*'
- 43: 253, # '+'
- 44: 253, # ','
- 45: 253, # '-'
- 46: 253, # '.'
- 47: 253, # '/'
- 48: 252, # '0'
- 49: 252, # '1'
- 50: 252, # '2'
- 51: 252, # '3'
- 52: 252, # '4'
- 53: 252, # '5'
- 54: 252, # '6'
- 55: 252, # '7'
- 56: 252, # '8'
- 57: 252, # '9'
- 58: 253, # ':'
- 59: 253, # ';'
- 60: 253, # '<'
- 61: 253, # '='
- 62: 253, # '>'
- 63: 253, # '?'
- 64: 253, # '@'
- 65: 28, # 'A'
- 66: 40, # 'B'
- 67: 54, # 'C'
- 68: 45, # 'D'
- 69: 32, # 'E'
- 70: 50, # 'F'
- 71: 49, # 'G'
- 72: 38, # 'H'
- 73: 39, # 'I'
- 74: 53, # 'J'
- 75: 36, # 'K'
- 76: 41, # 'L'
- 77: 34, # 'M'
- 78: 35, # 'N'
- 79: 47, # 'O'
- 80: 46, # 'P'
- 81: 71, # 'Q'
- 82: 43, # 'R'
- 83: 33, # 'S'
- 84: 37, # 'T'
- 85: 57, # 'U'
- 86: 48, # 'V'
- 87: 64, # 'W'
- 88: 68, # 'X'
- 89: 55, # 'Y'
- 90: 52, # 'Z'
- 91: 253, # '['
- 92: 253, # '\\'
- 93: 253, # ']'
- 94: 253, # '^'
- 95: 253, # '_'
- 96: 253, # '`'
- 97: 2, # 'a'
- 98: 18, # 'b'
- 99: 26, # 'c'
- 100: 17, # 'd'
- 101: 1, # 'e'
- 102: 27, # 'f'
- 103: 12, # 'g'
- 104: 20, # 'h'
- 105: 9, # 'i'
- 106: 22, # 'j'
- 107: 7, # 'k'
- 108: 6, # 'l'
- 109: 13, # 'm'
- 110: 4, # 'n'
- 111: 8, # 'o'
- 112: 23, # 'p'
- 113: 67, # 'q'
- 114: 10, # 'r'
- 115: 5, # 's'
- 116: 3, # 't'
- 117: 21, # 'u'
- 118: 19, # 'v'
- 119: 65, # 'w'
- 120: 62, # 'x'
- 121: 16, # 'y'
- 122: 11, # 'z'
- 123: 253, # '{'
- 124: 253, # '|'
- 125: 253, # '}'
- 126: 253, # '~'
- 127: 253, # '\x7f'
- 128: 159, # '\x80'
- 129: 160, # '\x81'
- 130: 161, # '\x82'
- 131: 162, # '\x83'
- 132: 163, # '\x84'
- 133: 164, # '\x85'
- 134: 165, # '\x86'
- 135: 166, # '\x87'
- 136: 167, # '\x88'
- 137: 168, # '\x89'
- 138: 169, # '\x8a'
- 139: 170, # '\x8b'
- 140: 171, # '\x8c'
- 141: 172, # '\x8d'
- 142: 173, # '\x8e'
- 143: 174, # '\x8f'
- 144: 175, # '\x90'
- 145: 176, # '\x91'
- 146: 177, # '\x92'
- 147: 178, # '\x93'
- 148: 179, # '\x94'
- 149: 180, # '\x95'
- 150: 181, # '\x96'
- 151: 182, # '\x97'
- 152: 183, # '\x98'
- 153: 184, # '\x99'
- 154: 185, # '\x9a'
- 155: 186, # '\x9b'
- 156: 187, # '\x9c'
- 157: 188, # '\x9d'
- 158: 189, # '\x9e'
- 159: 190, # '\x9f'
- 160: 191, # '\xa0'
- 161: 192, # 'Ą'
- 162: 193, # '˘'
- 163: 194, # 'Ł'
- 164: 195, # '¤'
- 165: 196, # 'Ľ'
- 166: 197, # 'Ś'
- 167: 75, # '§'
- 168: 198, # '¨'
- 169: 199, # 'Š'
- 170: 200, # 'Ş'
- 171: 201, # 'Ť'
- 172: 202, # 'Ź'
- 173: 203, # '\xad'
- 174: 204, # 'Ž'
- 175: 205, # 'Ż'
- 176: 79, # '°'
- 177: 206, # 'ą'
- 178: 207, # '˛'
- 179: 208, # 'ł'
- 180: 209, # '´'
- 181: 210, # 'ľ'
- 182: 211, # 'ś'
- 183: 212, # 'ˇ'
- 184: 213, # '¸'
- 185: 214, # 'š'
- 186: 215, # 'ş'
- 187: 216, # 'ť'
- 188: 217, # 'ź'
- 189: 218, # '˝'
- 190: 219, # 'ž'
- 191: 220, # 'ż'
- 192: 221, # 'Ŕ'
- 193: 51, # 'Á'
- 194: 81, # 'Â'
- 195: 222, # 'Ă'
- 196: 78, # 'Ä'
- 197: 223, # 'Ĺ'
- 198: 224, # 'Ć'
- 199: 225, # 'Ç'
- 200: 226, # 'Č'
- 201: 44, # 'É'
- 202: 227, # 'Ę'
- 203: 228, # 'Ë'
- 204: 229, # 'Ě'
- 205: 61, # 'Í'
- 206: 230, # 'Î'
- 207: 231, # 'Ď'
- 208: 232, # 'Đ'
- 209: 233, # 'Ń'
- 210: 234, # 'Ň'
- 211: 58, # 'Ó'
- 212: 235, # 'Ô'
- 213: 66, # 'Ő'
- 214: 59, # 'Ö'
- 215: 236, # '×'
- 216: 237, # 'Ř'
- 217: 238, # 'Ů'
- 218: 60, # 'Ú'
- 219: 69, # 'Ű'
- 220: 63, # 'Ü'
- 221: 239, # 'Ý'
- 222: 240, # 'Ţ'
- 223: 241, # 'ß'
- 224: 82, # 'ŕ'
- 225: 14, # 'á'
- 226: 74, # 'â'
- 227: 242, # 'ă'
- 228: 70, # 'ä'
- 229: 80, # 'ĺ'
- 230: 243, # 'ć'
- 231: 72, # 'ç'
- 232: 244, # 'č'
- 233: 15, # 'é'
- 234: 83, # 'ę'
- 235: 77, # 'ë'
- 236: 84, # 'ě'
- 237: 30, # 'í'
- 238: 76, # 'î'
- 239: 85, # 'ď'
- 240: 245, # 'đ'
- 241: 246, # 'ń'
- 242: 247, # 'ň'
- 243: 25, # 'ó'
- 244: 73, # 'ô'
- 245: 42, # 'ő'
- 246: 24, # 'ö'
- 247: 248, # '÷'
- 248: 249, # 'ř'
- 249: 250, # 'ů'
- 250: 31, # 'ú'
- 251: 56, # 'ű'
- 252: 29, # 'ü'
- 253: 251, # 'ý'
- 254: 252, # 'ţ'
- 255: 253, # '˙'
+ 0: 255, # '\x00'
+ 1: 255, # '\x01'
+ 2: 255, # '\x02'
+ 3: 255, # '\x03'
+ 4: 255, # '\x04'
+ 5: 255, # '\x05'
+ 6: 255, # '\x06'
+ 7: 255, # '\x07'
+ 8: 255, # '\x08'
+ 9: 255, # '\t'
+ 10: 254, # '\n'
+ 11: 255, # '\x0b'
+ 12: 255, # '\x0c'
+ 13: 254, # '\r'
+ 14: 255, # '\x0e'
+ 15: 255, # '\x0f'
+ 16: 255, # '\x10'
+ 17: 255, # '\x11'
+ 18: 255, # '\x12'
+ 19: 255, # '\x13'
+ 20: 255, # '\x14'
+ 21: 255, # '\x15'
+ 22: 255, # '\x16'
+ 23: 255, # '\x17'
+ 24: 255, # '\x18'
+ 25: 255, # '\x19'
+ 26: 255, # '\x1a'
+ 27: 255, # '\x1b'
+ 28: 255, # '\x1c'
+ 29: 255, # '\x1d'
+ 30: 255, # '\x1e'
+ 31: 255, # '\x1f'
+ 32: 253, # ' '
+ 33: 253, # '!'
+ 34: 253, # '"'
+ 35: 253, # '#'
+ 36: 253, # '$'
+ 37: 253, # '%'
+ 38: 253, # '&'
+ 39: 253, # "'"
+ 40: 253, # '('
+ 41: 253, # ')'
+ 42: 253, # '*'
+ 43: 253, # '+'
+ 44: 253, # ','
+ 45: 253, # '-'
+ 46: 253, # '.'
+ 47: 253, # '/'
+ 48: 252, # '0'
+ 49: 252, # '1'
+ 50: 252, # '2'
+ 51: 252, # '3'
+ 52: 252, # '4'
+ 53: 252, # '5'
+ 54: 252, # '6'
+ 55: 252, # '7'
+ 56: 252, # '8'
+ 57: 252, # '9'
+ 58: 253, # ':'
+ 59: 253, # ';'
+ 60: 253, # '<'
+ 61: 253, # '='
+ 62: 253, # '>'
+ 63: 253, # '?'
+ 64: 253, # '@'
+ 65: 28, # 'A'
+ 66: 40, # 'B'
+ 67: 54, # 'C'
+ 68: 45, # 'D'
+ 69: 32, # 'E'
+ 70: 50, # 'F'
+ 71: 49, # 'G'
+ 72: 38, # 'H'
+ 73: 39, # 'I'
+ 74: 53, # 'J'
+ 75: 36, # 'K'
+ 76: 41, # 'L'
+ 77: 34, # 'M'
+ 78: 35, # 'N'
+ 79: 47, # 'O'
+ 80: 46, # 'P'
+ 81: 71, # 'Q'
+ 82: 43, # 'R'
+ 83: 33, # 'S'
+ 84: 37, # 'T'
+ 85: 57, # 'U'
+ 86: 48, # 'V'
+ 87: 64, # 'W'
+ 88: 68, # 'X'
+ 89: 55, # 'Y'
+ 90: 52, # 'Z'
+ 91: 253, # '['
+ 92: 253, # '\\'
+ 93: 253, # ']'
+ 94: 253, # '^'
+ 95: 253, # '_'
+ 96: 253, # '`'
+ 97: 2, # 'a'
+ 98: 18, # 'b'
+ 99: 26, # 'c'
+ 100: 17, # 'd'
+ 101: 1, # 'e'
+ 102: 27, # 'f'
+ 103: 12, # 'g'
+ 104: 20, # 'h'
+ 105: 9, # 'i'
+ 106: 22, # 'j'
+ 107: 7, # 'k'
+ 108: 6, # 'l'
+ 109: 13, # 'm'
+ 110: 4, # 'n'
+ 111: 8, # 'o'
+ 112: 23, # 'p'
+ 113: 67, # 'q'
+ 114: 10, # 'r'
+ 115: 5, # 's'
+ 116: 3, # 't'
+ 117: 21, # 'u'
+ 118: 19, # 'v'
+ 119: 65, # 'w'
+ 120: 62, # 'x'
+ 121: 16, # 'y'
+ 122: 11, # 'z'
+ 123: 253, # '{'
+ 124: 253, # '|'
+ 125: 253, # '}'
+ 126: 253, # '~'
+ 127: 253, # '\x7f'
+ 128: 159, # '\x80'
+ 129: 160, # '\x81'
+ 130: 161, # '\x82'
+ 131: 162, # '\x83'
+ 132: 163, # '\x84'
+ 133: 164, # '\x85'
+ 134: 165, # '\x86'
+ 135: 166, # '\x87'
+ 136: 167, # '\x88'
+ 137: 168, # '\x89'
+ 138: 169, # '\x8a'
+ 139: 170, # '\x8b'
+ 140: 171, # '\x8c'
+ 141: 172, # '\x8d'
+ 142: 173, # '\x8e'
+ 143: 174, # '\x8f'
+ 144: 175, # '\x90'
+ 145: 176, # '\x91'
+ 146: 177, # '\x92'
+ 147: 178, # '\x93'
+ 148: 179, # '\x94'
+ 149: 180, # '\x95'
+ 150: 181, # '\x96'
+ 151: 182, # '\x97'
+ 152: 183, # '\x98'
+ 153: 184, # '\x99'
+ 154: 185, # '\x9a'
+ 155: 186, # '\x9b'
+ 156: 187, # '\x9c'
+ 157: 188, # '\x9d'
+ 158: 189, # '\x9e'
+ 159: 190, # '\x9f'
+ 160: 191, # '\xa0'
+ 161: 192, # 'Ą'
+ 162: 193, # '˘'
+ 163: 194, # 'Ł'
+ 164: 195, # '¤'
+ 165: 196, # 'Ľ'
+ 166: 197, # 'Ś'
+ 167: 75, # '§'
+ 168: 198, # '¨'
+ 169: 199, # 'Š'
+ 170: 200, # 'Ş'
+ 171: 201, # 'Ť'
+ 172: 202, # 'Ź'
+ 173: 203, # '\xad'
+ 174: 204, # 'Ž'
+ 175: 205, # 'Ż'
+ 176: 79, # '°'
+ 177: 206, # 'ą'
+ 178: 207, # '˛'
+ 179: 208, # 'ł'
+ 180: 209, # '´'
+ 181: 210, # 'ľ'
+ 182: 211, # 'ś'
+ 183: 212, # 'ˇ'
+ 184: 213, # '¸'
+ 185: 214, # 'š'
+ 186: 215, # 'ş'
+ 187: 216, # 'ť'
+ 188: 217, # 'ź'
+ 189: 218, # '˝'
+ 190: 219, # 'ž'
+ 191: 220, # 'ż'
+ 192: 221, # 'Ŕ'
+ 193: 51, # 'Á'
+ 194: 81, # 'Â'
+ 195: 222, # 'Ă'
+ 196: 78, # 'Ä'
+ 197: 223, # 'Ĺ'
+ 198: 224, # 'Ć'
+ 199: 225, # 'Ç'
+ 200: 226, # 'Č'
+ 201: 44, # 'É'
+ 202: 227, # 'Ę'
+ 203: 228, # 'Ë'
+ 204: 229, # 'Ě'
+ 205: 61, # 'Í'
+ 206: 230, # 'Î'
+ 207: 231, # 'Ď'
+ 208: 232, # 'Đ'
+ 209: 233, # 'Ń'
+ 210: 234, # 'Ň'
+ 211: 58, # 'Ó'
+ 212: 235, # 'Ô'
+ 213: 66, # 'Ő'
+ 214: 59, # 'Ö'
+ 215: 236, # '×'
+ 216: 237, # 'Ř'
+ 217: 238, # 'Ů'
+ 218: 60, # 'Ú'
+ 219: 69, # 'Ű'
+ 220: 63, # 'Ü'
+ 221: 239, # 'Ý'
+ 222: 240, # 'Ţ'
+ 223: 241, # 'ß'
+ 224: 82, # 'ŕ'
+ 225: 14, # 'á'
+ 226: 74, # 'â'
+ 227: 242, # 'ă'
+ 228: 70, # 'ä'
+ 229: 80, # 'ĺ'
+ 230: 243, # 'ć'
+ 231: 72, # 'ç'
+ 232: 244, # 'č'
+ 233: 15, # 'é'
+ 234: 83, # 'ę'
+ 235: 77, # 'ë'
+ 236: 84, # 'ě'
+ 237: 30, # 'í'
+ 238: 76, # 'î'
+ 239: 85, # 'ď'
+ 240: 245, # 'đ'
+ 241: 246, # 'ń'
+ 242: 247, # 'ň'
+ 243: 25, # 'ó'
+ 244: 73, # 'ô'
+ 245: 42, # 'ő'
+ 246: 24, # 'ö'
+ 247: 248, # '÷'
+ 248: 249, # 'ř'
+ 249: 250, # 'ů'
+ 250: 31, # 'ú'
+ 251: 56, # 'ű'
+ 252: 29, # 'ü'
+ 253: 251, # 'ý'
+ 254: 252, # 'ţ'
+ 255: 253, # '˙'
}
-ISO_8859_2_HUNGARIAN_MODEL = SingleByteCharSetModel(charset_name='ISO-8859-2',
- language='Hungarian',
- char_to_order_map=ISO_8859_2_HUNGARIAN_CHAR_TO_ORDER,
- language_model=HUNGARIAN_LANG_MODEL,
- typical_positive_ratio=0.947368,
- keep_ascii_letters=True,
- alphabet='ABCDEFGHIJKLMNOPRSTUVZabcdefghijklmnoprstuvzÁÉÍÓÖÚÜáéíóöúüŐőŰű')
-
+ISO_8859_2_HUNGARIAN_MODEL = SingleByteCharSetModel(
+ charset_name="ISO-8859-2",
+ language="Hungarian",
+ char_to_order_map=ISO_8859_2_HUNGARIAN_CHAR_TO_ORDER,
+ language_model=HUNGARIAN_LANG_MODEL,
+ typical_positive_ratio=0.947368,
+ keep_ascii_letters=True,
+ alphabet="ABCDEFGHIJKLMNOPRSTUVZabcdefghijklmnoprstuvzÁÉÍÓÖÚÜáéíóöúüŐőŰű",
+)
diff --git a/src/pip/_vendor/chardet/langrussianmodel.py b/src/pip/_vendor/chardet/langrussianmodel.py
index 5594452b5..39a538894 100644
--- a/src/pip/_vendor/chardet/langrussianmodel.py
+++ b/src/pip/_vendor/chardet/langrussianmodel.py
@@ -1,9 +1,5 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
from pip._vendor.chardet.sbcharsetprober import SingleByteCharSetModel
-
# 3: Positive
# 2: Likely
# 1: Unlikely
@@ -4115,1604 +4111,1615 @@ RUSSIAN_LANG_MODEL = {
# Character Mapping Table(s):
IBM866_RUSSIAN_CHAR_TO_ORDER = {
- 0: 255, # '\x00'
- 1: 255, # '\x01'
- 2: 255, # '\x02'
- 3: 255, # '\x03'
- 4: 255, # '\x04'
- 5: 255, # '\x05'
- 6: 255, # '\x06'
- 7: 255, # '\x07'
- 8: 255, # '\x08'
- 9: 255, # '\t'
- 10: 254, # '\n'
- 11: 255, # '\x0b'
- 12: 255, # '\x0c'
- 13: 254, # '\r'
- 14: 255, # '\x0e'
- 15: 255, # '\x0f'
- 16: 255, # '\x10'
- 17: 255, # '\x11'
- 18: 255, # '\x12'
- 19: 255, # '\x13'
- 20: 255, # '\x14'
- 21: 255, # '\x15'
- 22: 255, # '\x16'
- 23: 255, # '\x17'
- 24: 255, # '\x18'
- 25: 255, # '\x19'
- 26: 255, # '\x1a'
- 27: 255, # '\x1b'
- 28: 255, # '\x1c'
- 29: 255, # '\x1d'
- 30: 255, # '\x1e'
- 31: 255, # '\x1f'
- 32: 253, # ' '
- 33: 253, # '!'
- 34: 253, # '"'
- 35: 253, # '#'
- 36: 253, # '$'
- 37: 253, # '%'
- 38: 253, # '&'
- 39: 253, # "'"
- 40: 253, # '('
- 41: 253, # ')'
- 42: 253, # '*'
- 43: 253, # '+'
- 44: 253, # ','
- 45: 253, # '-'
- 46: 253, # '.'
- 47: 253, # '/'
- 48: 252, # '0'
- 49: 252, # '1'
- 50: 252, # '2'
- 51: 252, # '3'
- 52: 252, # '4'
- 53: 252, # '5'
- 54: 252, # '6'
- 55: 252, # '7'
- 56: 252, # '8'
- 57: 252, # '9'
- 58: 253, # ':'
- 59: 253, # ';'
- 60: 253, # '<'
- 61: 253, # '='
- 62: 253, # '>'
- 63: 253, # '?'
- 64: 253, # '@'
- 65: 142, # 'A'
- 66: 143, # 'B'
- 67: 144, # 'C'
- 68: 145, # 'D'
- 69: 146, # 'E'
- 70: 147, # 'F'
- 71: 148, # 'G'
- 72: 149, # 'H'
- 73: 150, # 'I'
- 74: 151, # 'J'
- 75: 152, # 'K'
- 76: 74, # 'L'
- 77: 153, # 'M'
- 78: 75, # 'N'
- 79: 154, # 'O'
- 80: 155, # 'P'
- 81: 156, # 'Q'
- 82: 157, # 'R'
- 83: 158, # 'S'
- 84: 159, # 'T'
- 85: 160, # 'U'
- 86: 161, # 'V'
- 87: 162, # 'W'
- 88: 163, # 'X'
- 89: 164, # 'Y'
- 90: 165, # 'Z'
- 91: 253, # '['
- 92: 253, # '\\'
- 93: 253, # ']'
- 94: 253, # '^'
- 95: 253, # '_'
- 96: 253, # '`'
- 97: 71, # 'a'
- 98: 172, # 'b'
- 99: 66, # 'c'
- 100: 173, # 'd'
- 101: 65, # 'e'
- 102: 174, # 'f'
- 103: 76, # 'g'
- 104: 175, # 'h'
- 105: 64, # 'i'
- 106: 176, # 'j'
- 107: 177, # 'k'
- 108: 77, # 'l'
- 109: 72, # 'm'
- 110: 178, # 'n'
- 111: 69, # 'o'
- 112: 67, # 'p'
- 113: 179, # 'q'
- 114: 78, # 'r'
- 115: 73, # 's'
- 116: 180, # 't'
- 117: 181, # 'u'
- 118: 79, # 'v'
- 119: 182, # 'w'
- 120: 183, # 'x'
- 121: 184, # 'y'
- 122: 185, # 'z'
- 123: 253, # '{'
- 124: 253, # '|'
- 125: 253, # '}'
- 126: 253, # '~'
- 127: 253, # '\x7f'
- 128: 37, # 'А'
- 129: 44, # 'Б'
- 130: 33, # 'В'
- 131: 46, # 'Г'
- 132: 41, # 'Д'
- 133: 48, # 'Е'
- 134: 56, # 'Ж'
- 135: 51, # 'З'
- 136: 42, # 'И'
- 137: 60, # 'Й'
- 138: 36, # 'К'
- 139: 49, # 'Л'
- 140: 38, # 'М'
- 141: 31, # 'Н'
- 142: 34, # 'О'
- 143: 35, # 'П'
- 144: 45, # 'Р'
- 145: 32, # 'С'
- 146: 40, # 'Т'
- 147: 52, # 'У'
- 148: 53, # 'Ф'
- 149: 55, # 'Х'
- 150: 58, # 'Ц'
- 151: 50, # 'Ч'
- 152: 57, # 'Ш'
- 153: 63, # 'Щ'
- 154: 70, # 'Ъ'
- 155: 62, # 'Ы'
- 156: 61, # 'Ь'
- 157: 47, # 'Э'
- 158: 59, # 'Ю'
- 159: 43, # 'Я'
- 160: 3, # 'а'
- 161: 21, # 'б'
- 162: 10, # 'в'
- 163: 19, # 'г'
- 164: 13, # 'д'
- 165: 2, # 'е'
- 166: 24, # 'ж'
- 167: 20, # 'з'
- 168: 4, # 'и'
- 169: 23, # 'й'
- 170: 11, # 'к'
- 171: 8, # 'л'
- 172: 12, # 'м'
- 173: 5, # 'н'
- 174: 1, # 'о'
- 175: 15, # 'п'
- 176: 191, # '░'
- 177: 192, # '▒'
- 178: 193, # '▓'
- 179: 194, # '│'
- 180: 195, # '┤'
- 181: 196, # '╡'
- 182: 197, # '╢'
- 183: 198, # '╖'
- 184: 199, # '╕'
- 185: 200, # '╣'
- 186: 201, # '║'
- 187: 202, # '╗'
- 188: 203, # '╝'
- 189: 204, # '╜'
- 190: 205, # '╛'
- 191: 206, # '┐'
- 192: 207, # '└'
- 193: 208, # '┴'
- 194: 209, # '┬'
- 195: 210, # '├'
- 196: 211, # '─'
- 197: 212, # '┼'
- 198: 213, # '╞'
- 199: 214, # '╟'
- 200: 215, # '╚'
- 201: 216, # '╔'
- 202: 217, # '╩'
- 203: 218, # '╦'
- 204: 219, # '╠'
- 205: 220, # '═'
- 206: 221, # '╬'
- 207: 222, # '╧'
- 208: 223, # '╨'
- 209: 224, # '╤'
- 210: 225, # '╥'
- 211: 226, # '╙'
- 212: 227, # '╘'
- 213: 228, # '╒'
- 214: 229, # '╓'
- 215: 230, # '╫'
- 216: 231, # '╪'
- 217: 232, # '┘'
- 218: 233, # '┌'
- 219: 234, # '█'
- 220: 235, # '▄'
- 221: 236, # '▌'
- 222: 237, # '▐'
- 223: 238, # '▀'
- 224: 9, # 'р'
- 225: 7, # 'с'
- 226: 6, # 'т'
- 227: 14, # 'у'
- 228: 39, # 'ф'
- 229: 26, # 'х'
- 230: 28, # 'ц'
- 231: 22, # 'ч'
- 232: 25, # 'ш'
- 233: 29, # 'щ'
- 234: 54, # 'ъ'
- 235: 18, # 'ы'
- 236: 17, # 'ь'
- 237: 30, # 'э'
- 238: 27, # 'ю'
- 239: 16, # 'я'
- 240: 239, # 'Ё'
- 241: 68, # 'ё'
- 242: 240, # 'Є'
- 243: 241, # 'є'
- 244: 242, # 'Ї'
- 245: 243, # 'ї'
- 246: 244, # 'Ў'
- 247: 245, # 'ў'
- 248: 246, # '°'
- 249: 247, # '∙'
- 250: 248, # '·'
- 251: 249, # '√'
- 252: 250, # '№'
- 253: 251, # '¤'
- 254: 252, # '■'
- 255: 255, # '\xa0'
+ 0: 255, # '\x00'
+ 1: 255, # '\x01'
+ 2: 255, # '\x02'
+ 3: 255, # '\x03'
+ 4: 255, # '\x04'
+ 5: 255, # '\x05'
+ 6: 255, # '\x06'
+ 7: 255, # '\x07'
+ 8: 255, # '\x08'
+ 9: 255, # '\t'
+ 10: 254, # '\n'
+ 11: 255, # '\x0b'
+ 12: 255, # '\x0c'
+ 13: 254, # '\r'
+ 14: 255, # '\x0e'
+ 15: 255, # '\x0f'
+ 16: 255, # '\x10'
+ 17: 255, # '\x11'
+ 18: 255, # '\x12'
+ 19: 255, # '\x13'
+ 20: 255, # '\x14'
+ 21: 255, # '\x15'
+ 22: 255, # '\x16'
+ 23: 255, # '\x17'
+ 24: 255, # '\x18'
+ 25: 255, # '\x19'
+ 26: 255, # '\x1a'
+ 27: 255, # '\x1b'
+ 28: 255, # '\x1c'
+ 29: 255, # '\x1d'
+ 30: 255, # '\x1e'
+ 31: 255, # '\x1f'
+ 32: 253, # ' '
+ 33: 253, # '!'
+ 34: 253, # '"'
+ 35: 253, # '#'
+ 36: 253, # '$'
+ 37: 253, # '%'
+ 38: 253, # '&'
+ 39: 253, # "'"
+ 40: 253, # '('
+ 41: 253, # ')'
+ 42: 253, # '*'
+ 43: 253, # '+'
+ 44: 253, # ','
+ 45: 253, # '-'
+ 46: 253, # '.'
+ 47: 253, # '/'
+ 48: 252, # '0'
+ 49: 252, # '1'
+ 50: 252, # '2'
+ 51: 252, # '3'
+ 52: 252, # '4'
+ 53: 252, # '5'
+ 54: 252, # '6'
+ 55: 252, # '7'
+ 56: 252, # '8'
+ 57: 252, # '9'
+ 58: 253, # ':'
+ 59: 253, # ';'
+ 60: 253, # '<'
+ 61: 253, # '='
+ 62: 253, # '>'
+ 63: 253, # '?'
+ 64: 253, # '@'
+ 65: 142, # 'A'
+ 66: 143, # 'B'
+ 67: 144, # 'C'
+ 68: 145, # 'D'
+ 69: 146, # 'E'
+ 70: 147, # 'F'
+ 71: 148, # 'G'
+ 72: 149, # 'H'
+ 73: 150, # 'I'
+ 74: 151, # 'J'
+ 75: 152, # 'K'
+ 76: 74, # 'L'
+ 77: 153, # 'M'
+ 78: 75, # 'N'
+ 79: 154, # 'O'
+ 80: 155, # 'P'
+ 81: 156, # 'Q'
+ 82: 157, # 'R'
+ 83: 158, # 'S'
+ 84: 159, # 'T'
+ 85: 160, # 'U'
+ 86: 161, # 'V'
+ 87: 162, # 'W'
+ 88: 163, # 'X'
+ 89: 164, # 'Y'
+ 90: 165, # 'Z'
+ 91: 253, # '['
+ 92: 253, # '\\'
+ 93: 253, # ']'
+ 94: 253, # '^'
+ 95: 253, # '_'
+ 96: 253, # '`'
+ 97: 71, # 'a'
+ 98: 172, # 'b'
+ 99: 66, # 'c'
+ 100: 173, # 'd'
+ 101: 65, # 'e'
+ 102: 174, # 'f'
+ 103: 76, # 'g'
+ 104: 175, # 'h'
+ 105: 64, # 'i'
+ 106: 176, # 'j'
+ 107: 177, # 'k'
+ 108: 77, # 'l'
+ 109: 72, # 'm'
+ 110: 178, # 'n'
+ 111: 69, # 'o'
+ 112: 67, # 'p'
+ 113: 179, # 'q'
+ 114: 78, # 'r'
+ 115: 73, # 's'
+ 116: 180, # 't'
+ 117: 181, # 'u'
+ 118: 79, # 'v'
+ 119: 182, # 'w'
+ 120: 183, # 'x'
+ 121: 184, # 'y'
+ 122: 185, # 'z'
+ 123: 253, # '{'
+ 124: 253, # '|'
+ 125: 253, # '}'
+ 126: 253, # '~'
+ 127: 253, # '\x7f'
+ 128: 37, # 'А'
+ 129: 44, # 'Б'
+ 130: 33, # 'В'
+ 131: 46, # 'Г'
+ 132: 41, # 'Д'
+ 133: 48, # 'Е'
+ 134: 56, # 'Ж'
+ 135: 51, # 'З'
+ 136: 42, # 'И'
+ 137: 60, # 'Й'
+ 138: 36, # 'К'
+ 139: 49, # 'Л'
+ 140: 38, # 'М'
+ 141: 31, # 'Н'
+ 142: 34, # 'О'
+ 143: 35, # 'П'
+ 144: 45, # 'Р'
+ 145: 32, # 'С'
+ 146: 40, # 'Т'
+ 147: 52, # 'У'
+ 148: 53, # 'Ф'
+ 149: 55, # 'Х'
+ 150: 58, # 'Ц'
+ 151: 50, # 'Ч'
+ 152: 57, # 'Ш'
+ 153: 63, # 'Щ'
+ 154: 70, # 'Ъ'
+ 155: 62, # 'Ы'
+ 156: 61, # 'Ь'
+ 157: 47, # 'Э'
+ 158: 59, # 'Ю'
+ 159: 43, # 'Я'
+ 160: 3, # 'а'
+ 161: 21, # 'б'
+ 162: 10, # 'в'
+ 163: 19, # 'г'
+ 164: 13, # 'д'
+ 165: 2, # 'е'
+ 166: 24, # 'ж'
+ 167: 20, # 'з'
+ 168: 4, # 'и'
+ 169: 23, # 'й'
+ 170: 11, # 'к'
+ 171: 8, # 'л'
+ 172: 12, # 'м'
+ 173: 5, # 'н'
+ 174: 1, # 'о'
+ 175: 15, # 'п'
+ 176: 191, # '░'
+ 177: 192, # '▒'
+ 178: 193, # '▓'
+ 179: 194, # '│'
+ 180: 195, # '┤'
+ 181: 196, # '╡'
+ 182: 197, # '╢'
+ 183: 198, # '╖'
+ 184: 199, # '╕'
+ 185: 200, # '╣'
+ 186: 201, # '║'
+ 187: 202, # '╗'
+ 188: 203, # '╝'
+ 189: 204, # '╜'
+ 190: 205, # '╛'
+ 191: 206, # '┐'
+ 192: 207, # '└'
+ 193: 208, # '┴'
+ 194: 209, # '┬'
+ 195: 210, # '├'
+ 196: 211, # '─'
+ 197: 212, # '┼'
+ 198: 213, # '╞'
+ 199: 214, # '╟'
+ 200: 215, # '╚'
+ 201: 216, # '╔'
+ 202: 217, # '╩'
+ 203: 218, # '╦'
+ 204: 219, # '╠'
+ 205: 220, # '═'
+ 206: 221, # '╬'
+ 207: 222, # '╧'
+ 208: 223, # '╨'
+ 209: 224, # '╤'
+ 210: 225, # '╥'
+ 211: 226, # '╙'
+ 212: 227, # '╘'
+ 213: 228, # '╒'
+ 214: 229, # '╓'
+ 215: 230, # '╫'
+ 216: 231, # '╪'
+ 217: 232, # '┘'
+ 218: 233, # '┌'
+ 219: 234, # '█'
+ 220: 235, # '▄'
+ 221: 236, # '▌'
+ 222: 237, # '▐'
+ 223: 238, # '▀'
+ 224: 9, # 'р'
+ 225: 7, # 'с'
+ 226: 6, # 'т'
+ 227: 14, # 'у'
+ 228: 39, # 'ф'
+ 229: 26, # 'х'
+ 230: 28, # 'ц'
+ 231: 22, # 'ч'
+ 232: 25, # 'ш'
+ 233: 29, # 'щ'
+ 234: 54, # 'ъ'
+ 235: 18, # 'ы'
+ 236: 17, # 'ь'
+ 237: 30, # 'э'
+ 238: 27, # 'ю'
+ 239: 16, # 'я'
+ 240: 239, # 'Ё'
+ 241: 68, # 'ё'
+ 242: 240, # 'Є'
+ 243: 241, # 'є'
+ 244: 242, # 'Ї'
+ 245: 243, # 'ї'
+ 246: 244, # 'Ў'
+ 247: 245, # 'ў'
+ 248: 246, # '°'
+ 249: 247, # '∙'
+ 250: 248, # '·'
+ 251: 249, # '√'
+ 252: 250, # '№'
+ 253: 251, # '¤'
+ 254: 252, # '■'
+ 255: 255, # '\xa0'
}
-IBM866_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='IBM866',
- language='Russian',
- char_to_order_map=IBM866_RUSSIAN_CHAR_TO_ORDER,
- language_model=RUSSIAN_LANG_MODEL,
- typical_positive_ratio=0.976601,
- keep_ascii_letters=False,
- alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё')
+IBM866_RUSSIAN_MODEL = SingleByteCharSetModel(
+ charset_name="IBM866",
+ language="Russian",
+ char_to_order_map=IBM866_RUSSIAN_CHAR_TO_ORDER,
+ language_model=RUSSIAN_LANG_MODEL,
+ typical_positive_ratio=0.976601,
+ keep_ascii_letters=False,
+ alphabet="ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё",
+)
WINDOWS_1251_RUSSIAN_CHAR_TO_ORDER = {
- 0: 255, # '\x00'
- 1: 255, # '\x01'
- 2: 255, # '\x02'
- 3: 255, # '\x03'
- 4: 255, # '\x04'
- 5: 255, # '\x05'
- 6: 255, # '\x06'
- 7: 255, # '\x07'
- 8: 255, # '\x08'
- 9: 255, # '\t'
- 10: 254, # '\n'
- 11: 255, # '\x0b'
- 12: 255, # '\x0c'
- 13: 254, # '\r'
- 14: 255, # '\x0e'
- 15: 255, # '\x0f'
- 16: 255, # '\x10'
- 17: 255, # '\x11'
- 18: 255, # '\x12'
- 19: 255, # '\x13'
- 20: 255, # '\x14'
- 21: 255, # '\x15'
- 22: 255, # '\x16'
- 23: 255, # '\x17'
- 24: 255, # '\x18'
- 25: 255, # '\x19'
- 26: 255, # '\x1a'
- 27: 255, # '\x1b'
- 28: 255, # '\x1c'
- 29: 255, # '\x1d'
- 30: 255, # '\x1e'
- 31: 255, # '\x1f'
- 32: 253, # ' '
- 33: 253, # '!'
- 34: 253, # '"'
- 35: 253, # '#'
- 36: 253, # '$'
- 37: 253, # '%'
- 38: 253, # '&'
- 39: 253, # "'"
- 40: 253, # '('
- 41: 253, # ')'
- 42: 253, # '*'
- 43: 253, # '+'
- 44: 253, # ','
- 45: 253, # '-'
- 46: 253, # '.'
- 47: 253, # '/'
- 48: 252, # '0'
- 49: 252, # '1'
- 50: 252, # '2'
- 51: 252, # '3'
- 52: 252, # '4'
- 53: 252, # '5'
- 54: 252, # '6'
- 55: 252, # '7'
- 56: 252, # '8'
- 57: 252, # '9'
- 58: 253, # ':'
- 59: 253, # ';'
- 60: 253, # '<'
- 61: 253, # '='
- 62: 253, # '>'
- 63: 253, # '?'
- 64: 253, # '@'
- 65: 142, # 'A'
- 66: 143, # 'B'
- 67: 144, # 'C'
- 68: 145, # 'D'
- 69: 146, # 'E'
- 70: 147, # 'F'
- 71: 148, # 'G'
- 72: 149, # 'H'
- 73: 150, # 'I'
- 74: 151, # 'J'
- 75: 152, # 'K'
- 76: 74, # 'L'
- 77: 153, # 'M'
- 78: 75, # 'N'
- 79: 154, # 'O'
- 80: 155, # 'P'
- 81: 156, # 'Q'
- 82: 157, # 'R'
- 83: 158, # 'S'
- 84: 159, # 'T'
- 85: 160, # 'U'
- 86: 161, # 'V'
- 87: 162, # 'W'
- 88: 163, # 'X'
- 89: 164, # 'Y'
- 90: 165, # 'Z'
- 91: 253, # '['
- 92: 253, # '\\'
- 93: 253, # ']'
- 94: 253, # '^'
- 95: 253, # '_'
- 96: 253, # '`'
- 97: 71, # 'a'
- 98: 172, # 'b'
- 99: 66, # 'c'
- 100: 173, # 'd'
- 101: 65, # 'e'
- 102: 174, # 'f'
- 103: 76, # 'g'
- 104: 175, # 'h'
- 105: 64, # 'i'
- 106: 176, # 'j'
- 107: 177, # 'k'
- 108: 77, # 'l'
- 109: 72, # 'm'
- 110: 178, # 'n'
- 111: 69, # 'o'
- 112: 67, # 'p'
- 113: 179, # 'q'
- 114: 78, # 'r'
- 115: 73, # 's'
- 116: 180, # 't'
- 117: 181, # 'u'
- 118: 79, # 'v'
- 119: 182, # 'w'
- 120: 183, # 'x'
- 121: 184, # 'y'
- 122: 185, # 'z'
- 123: 253, # '{'
- 124: 253, # '|'
- 125: 253, # '}'
- 126: 253, # '~'
- 127: 253, # '\x7f'
- 128: 191, # 'Ђ'
- 129: 192, # 'Ѓ'
- 130: 193, # '‚'
- 131: 194, # 'ѓ'
- 132: 195, # '„'
- 133: 196, # '…'
- 134: 197, # '†'
- 135: 198, # '‡'
- 136: 199, # '€'
- 137: 200, # '‰'
- 138: 201, # 'Љ'
- 139: 202, # '‹'
- 140: 203, # 'Њ'
- 141: 204, # 'Ќ'
- 142: 205, # 'Ћ'
- 143: 206, # 'Џ'
- 144: 207, # 'ђ'
- 145: 208, # '‘'
- 146: 209, # '’'
- 147: 210, # '“'
- 148: 211, # '”'
- 149: 212, # '•'
- 150: 213, # '–'
- 151: 214, # '—'
- 152: 215, # None
- 153: 216, # '™'
- 154: 217, # 'љ'
- 155: 218, # '›'
- 156: 219, # 'њ'
- 157: 220, # 'ќ'
- 158: 221, # 'ћ'
- 159: 222, # 'џ'
- 160: 223, # '\xa0'
- 161: 224, # 'Ў'
- 162: 225, # 'ў'
- 163: 226, # 'Ј'
- 164: 227, # '¤'
- 165: 228, # 'Ґ'
- 166: 229, # '¦'
- 167: 230, # '§'
- 168: 231, # 'Ё'
- 169: 232, # '©'
- 170: 233, # 'Є'
- 171: 234, # '«'
- 172: 235, # '¬'
- 173: 236, # '\xad'
- 174: 237, # '®'
- 175: 238, # 'Ї'
- 176: 239, # '°'
- 177: 240, # '±'
- 178: 241, # 'І'
- 179: 242, # 'і'
- 180: 243, # 'ґ'
- 181: 244, # 'µ'
- 182: 245, # '¶'
- 183: 246, # '·'
- 184: 68, # 'ё'
- 185: 247, # '№'
- 186: 248, # 'є'
- 187: 249, # '»'
- 188: 250, # 'ј'
- 189: 251, # 'Ѕ'
- 190: 252, # 'ѕ'
- 191: 253, # 'ї'
- 192: 37, # 'А'
- 193: 44, # 'Б'
- 194: 33, # 'В'
- 195: 46, # 'Г'
- 196: 41, # 'Д'
- 197: 48, # 'Е'
- 198: 56, # 'Ж'
- 199: 51, # 'З'
- 200: 42, # 'И'
- 201: 60, # 'Й'
- 202: 36, # 'К'
- 203: 49, # 'Л'
- 204: 38, # 'М'
- 205: 31, # 'Н'
- 206: 34, # 'О'
- 207: 35, # 'П'
- 208: 45, # 'Р'
- 209: 32, # 'С'
- 210: 40, # 'Т'
- 211: 52, # 'У'
- 212: 53, # 'Ф'
- 213: 55, # 'Х'
- 214: 58, # 'Ц'
- 215: 50, # 'Ч'
- 216: 57, # 'Ш'
- 217: 63, # 'Щ'
- 218: 70, # 'Ъ'
- 219: 62, # 'Ы'
- 220: 61, # 'Ь'
- 221: 47, # 'Э'
- 222: 59, # 'Ю'
- 223: 43, # 'Я'
- 224: 3, # 'а'
- 225: 21, # 'б'
- 226: 10, # 'в'
- 227: 19, # 'г'
- 228: 13, # 'д'
- 229: 2, # 'е'
- 230: 24, # 'ж'
- 231: 20, # 'з'
- 232: 4, # 'и'
- 233: 23, # 'й'
- 234: 11, # 'к'
- 235: 8, # 'л'
- 236: 12, # 'м'
- 237: 5, # 'н'
- 238: 1, # 'о'
- 239: 15, # 'п'
- 240: 9, # 'р'
- 241: 7, # 'с'
- 242: 6, # 'т'
- 243: 14, # 'у'
- 244: 39, # 'ф'
- 245: 26, # 'х'
- 246: 28, # 'ц'
- 247: 22, # 'ч'
- 248: 25, # 'ш'
- 249: 29, # 'щ'
- 250: 54, # 'ъ'
- 251: 18, # 'ы'
- 252: 17, # 'ь'
- 253: 30, # 'э'
- 254: 27, # 'ю'
- 255: 16, # 'я'
+ 0: 255, # '\x00'
+ 1: 255, # '\x01'
+ 2: 255, # '\x02'
+ 3: 255, # '\x03'
+ 4: 255, # '\x04'
+ 5: 255, # '\x05'
+ 6: 255, # '\x06'
+ 7: 255, # '\x07'
+ 8: 255, # '\x08'
+ 9: 255, # '\t'
+ 10: 254, # '\n'
+ 11: 255, # '\x0b'
+ 12: 255, # '\x0c'
+ 13: 254, # '\r'
+ 14: 255, # '\x0e'
+ 15: 255, # '\x0f'
+ 16: 255, # '\x10'
+ 17: 255, # '\x11'
+ 18: 255, # '\x12'
+ 19: 255, # '\x13'
+ 20: 255, # '\x14'
+ 21: 255, # '\x15'
+ 22: 255, # '\x16'
+ 23: 255, # '\x17'
+ 24: 255, # '\x18'
+ 25: 255, # '\x19'
+ 26: 255, # '\x1a'
+ 27: 255, # '\x1b'
+ 28: 255, # '\x1c'
+ 29: 255, # '\x1d'
+ 30: 255, # '\x1e'
+ 31: 255, # '\x1f'
+ 32: 253, # ' '
+ 33: 253, # '!'
+ 34: 253, # '"'
+ 35: 253, # '#'
+ 36: 253, # '$'
+ 37: 253, # '%'
+ 38: 253, # '&'
+ 39: 253, # "'"
+ 40: 253, # '('
+ 41: 253, # ')'
+ 42: 253, # '*'
+ 43: 253, # '+'
+ 44: 253, # ','
+ 45: 253, # '-'
+ 46: 253, # '.'
+ 47: 253, # '/'
+ 48: 252, # '0'
+ 49: 252, # '1'
+ 50: 252, # '2'
+ 51: 252, # '3'
+ 52: 252, # '4'
+ 53: 252, # '5'
+ 54: 252, # '6'
+ 55: 252, # '7'
+ 56: 252, # '8'
+ 57: 252, # '9'
+ 58: 253, # ':'
+ 59: 253, # ';'
+ 60: 253, # '<'
+ 61: 253, # '='
+ 62: 253, # '>'
+ 63: 253, # '?'
+ 64: 253, # '@'
+ 65: 142, # 'A'
+ 66: 143, # 'B'
+ 67: 144, # 'C'
+ 68: 145, # 'D'
+ 69: 146, # 'E'
+ 70: 147, # 'F'
+ 71: 148, # 'G'
+ 72: 149, # 'H'
+ 73: 150, # 'I'
+ 74: 151, # 'J'
+ 75: 152, # 'K'
+ 76: 74, # 'L'
+ 77: 153, # 'M'
+ 78: 75, # 'N'
+ 79: 154, # 'O'
+ 80: 155, # 'P'
+ 81: 156, # 'Q'
+ 82: 157, # 'R'
+ 83: 158, # 'S'
+ 84: 159, # 'T'
+ 85: 160, # 'U'
+ 86: 161, # 'V'
+ 87: 162, # 'W'
+ 88: 163, # 'X'
+ 89: 164, # 'Y'
+ 90: 165, # 'Z'
+ 91: 253, # '['
+ 92: 253, # '\\'
+ 93: 253, # ']'
+ 94: 253, # '^'
+ 95: 253, # '_'
+ 96: 253, # '`'
+ 97: 71, # 'a'
+ 98: 172, # 'b'
+ 99: 66, # 'c'
+ 100: 173, # 'd'
+ 101: 65, # 'e'
+ 102: 174, # 'f'
+ 103: 76, # 'g'
+ 104: 175, # 'h'
+ 105: 64, # 'i'
+ 106: 176, # 'j'
+ 107: 177, # 'k'
+ 108: 77, # 'l'
+ 109: 72, # 'm'
+ 110: 178, # 'n'
+ 111: 69, # 'o'
+ 112: 67, # 'p'
+ 113: 179, # 'q'
+ 114: 78, # 'r'
+ 115: 73, # 's'
+ 116: 180, # 't'
+ 117: 181, # 'u'
+ 118: 79, # 'v'
+ 119: 182, # 'w'
+ 120: 183, # 'x'
+ 121: 184, # 'y'
+ 122: 185, # 'z'
+ 123: 253, # '{'
+ 124: 253, # '|'
+ 125: 253, # '}'
+ 126: 253, # '~'
+ 127: 253, # '\x7f'
+ 128: 191, # 'Ђ'
+ 129: 192, # 'Ѓ'
+ 130: 193, # '‚'
+ 131: 194, # 'ѓ'
+ 132: 195, # '„'
+ 133: 196, # '…'
+ 134: 197, # '†'
+ 135: 198, # '‡'
+ 136: 199, # '€'
+ 137: 200, # '‰'
+ 138: 201, # 'Љ'
+ 139: 202, # '‹'
+ 140: 203, # 'Њ'
+ 141: 204, # 'Ќ'
+ 142: 205, # 'Ћ'
+ 143: 206, # 'Џ'
+ 144: 207, # 'ђ'
+ 145: 208, # '‘'
+ 146: 209, # '’'
+ 147: 210, # '“'
+ 148: 211, # '”'
+ 149: 212, # '•'
+ 150: 213, # '–'
+ 151: 214, # '—'
+ 152: 215, # None
+ 153: 216, # '™'
+ 154: 217, # 'љ'
+ 155: 218, # '›'
+ 156: 219, # 'њ'
+ 157: 220, # 'ќ'
+ 158: 221, # 'ћ'
+ 159: 222, # 'џ'
+ 160: 223, # '\xa0'
+ 161: 224, # 'Ў'
+ 162: 225, # 'ў'
+ 163: 226, # 'Ј'
+ 164: 227, # '¤'
+ 165: 228, # 'Ґ'
+ 166: 229, # '¦'
+ 167: 230, # '§'
+ 168: 231, # 'Ё'
+ 169: 232, # '©'
+ 170: 233, # 'Є'
+ 171: 234, # '«'
+ 172: 235, # '¬'
+ 173: 236, # '\xad'
+ 174: 237, # '®'
+ 175: 238, # 'Ї'
+ 176: 239, # '°'
+ 177: 240, # '±'
+ 178: 241, # 'І'
+ 179: 242, # 'і'
+ 180: 243, # 'ґ'
+ 181: 244, # 'µ'
+ 182: 245, # '¶'
+ 183: 246, # '·'
+ 184: 68, # 'ё'
+ 185: 247, # '№'
+ 186: 248, # 'є'
+ 187: 249, # '»'
+ 188: 250, # 'ј'
+ 189: 251, # 'Ѕ'
+ 190: 252, # 'ѕ'
+ 191: 253, # 'ї'
+ 192: 37, # 'А'
+ 193: 44, # 'Б'
+ 194: 33, # 'В'
+ 195: 46, # 'Г'
+ 196: 41, # 'Д'
+ 197: 48, # 'Е'
+ 198: 56, # 'Ж'
+ 199: 51, # 'З'
+ 200: 42, # 'И'
+ 201: 60, # 'Й'
+ 202: 36, # 'К'
+ 203: 49, # 'Л'
+ 204: 38, # 'М'
+ 205: 31, # 'Н'
+ 206: 34, # 'О'
+ 207: 35, # 'П'
+ 208: 45, # 'Р'
+ 209: 32, # 'С'
+ 210: 40, # 'Т'
+ 211: 52, # 'У'
+ 212: 53, # 'Ф'
+ 213: 55, # 'Х'
+ 214: 58, # 'Ц'
+ 215: 50, # 'Ч'
+ 216: 57, # 'Ш'
+ 217: 63, # 'Щ'
+ 218: 70, # 'Ъ'
+ 219: 62, # 'Ы'
+ 220: 61, # 'Ь'
+ 221: 47, # 'Э'
+ 222: 59, # 'Ю'
+ 223: 43, # 'Я'
+ 224: 3, # 'а'
+ 225: 21, # 'б'
+ 226: 10, # 'в'
+ 227: 19, # 'г'
+ 228: 13, # 'д'
+ 229: 2, # 'е'
+ 230: 24, # 'ж'
+ 231: 20, # 'з'
+ 232: 4, # 'и'
+ 233: 23, # 'й'
+ 234: 11, # 'к'
+ 235: 8, # 'л'
+ 236: 12, # 'м'
+ 237: 5, # 'н'
+ 238: 1, # 'о'
+ 239: 15, # 'п'
+ 240: 9, # 'р'
+ 241: 7, # 'с'
+ 242: 6, # 'т'
+ 243: 14, # 'у'
+ 244: 39, # 'ф'
+ 245: 26, # 'х'
+ 246: 28, # 'ц'
+ 247: 22, # 'ч'
+ 248: 25, # 'ш'
+ 249: 29, # 'щ'
+ 250: 54, # 'ъ'
+ 251: 18, # 'ы'
+ 252: 17, # 'ь'
+ 253: 30, # 'э'
+ 254: 27, # 'ю'
+ 255: 16, # 'я'
}
-WINDOWS_1251_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='windows-1251',
- language='Russian',
- char_to_order_map=WINDOWS_1251_RUSSIAN_CHAR_TO_ORDER,
- language_model=RUSSIAN_LANG_MODEL,
- typical_positive_ratio=0.976601,
- keep_ascii_letters=False,
- alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё')
+WINDOWS_1251_RUSSIAN_MODEL = SingleByteCharSetModel(
+ charset_name="windows-1251",
+ language="Russian",
+ char_to_order_map=WINDOWS_1251_RUSSIAN_CHAR_TO_ORDER,
+ language_model=RUSSIAN_LANG_MODEL,
+ typical_positive_ratio=0.976601,
+ keep_ascii_letters=False,
+ alphabet="ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё",
+)
IBM855_RUSSIAN_CHAR_TO_ORDER = {
- 0: 255, # '\x00'
- 1: 255, # '\x01'
- 2: 255, # '\x02'
- 3: 255, # '\x03'
- 4: 255, # '\x04'
- 5: 255, # '\x05'
- 6: 255, # '\x06'
- 7: 255, # '\x07'
- 8: 255, # '\x08'
- 9: 255, # '\t'
- 10: 254, # '\n'
- 11: 255, # '\x0b'
- 12: 255, # '\x0c'
- 13: 254, # '\r'
- 14: 255, # '\x0e'
- 15: 255, # '\x0f'
- 16: 255, # '\x10'
- 17: 255, # '\x11'
- 18: 255, # '\x12'
- 19: 255, # '\x13'
- 20: 255, # '\x14'
- 21: 255, # '\x15'
- 22: 255, # '\x16'
- 23: 255, # '\x17'
- 24: 255, # '\x18'
- 25: 255, # '\x19'
- 26: 255, # '\x1a'
- 27: 255, # '\x1b'
- 28: 255, # '\x1c'
- 29: 255, # '\x1d'
- 30: 255, # '\x1e'
- 31: 255, # '\x1f'
- 32: 253, # ' '
- 33: 253, # '!'
- 34: 253, # '"'
- 35: 253, # '#'
- 36: 253, # '$'
- 37: 253, # '%'
- 38: 253, # '&'
- 39: 253, # "'"
- 40: 253, # '('
- 41: 253, # ')'
- 42: 253, # '*'
- 43: 253, # '+'
- 44: 253, # ','
- 45: 253, # '-'
- 46: 253, # '.'
- 47: 253, # '/'
- 48: 252, # '0'
- 49: 252, # '1'
- 50: 252, # '2'
- 51: 252, # '3'
- 52: 252, # '4'
- 53: 252, # '5'
- 54: 252, # '6'
- 55: 252, # '7'
- 56: 252, # '8'
- 57: 252, # '9'
- 58: 253, # ':'
- 59: 253, # ';'
- 60: 253, # '<'
- 61: 253, # '='
- 62: 253, # '>'
- 63: 253, # '?'
- 64: 253, # '@'
- 65: 142, # 'A'
- 66: 143, # 'B'
- 67: 144, # 'C'
- 68: 145, # 'D'
- 69: 146, # 'E'
- 70: 147, # 'F'
- 71: 148, # 'G'
- 72: 149, # 'H'
- 73: 150, # 'I'
- 74: 151, # 'J'
- 75: 152, # 'K'
- 76: 74, # 'L'
- 77: 153, # 'M'
- 78: 75, # 'N'
- 79: 154, # 'O'
- 80: 155, # 'P'
- 81: 156, # 'Q'
- 82: 157, # 'R'
- 83: 158, # 'S'
- 84: 159, # 'T'
- 85: 160, # 'U'
- 86: 161, # 'V'
- 87: 162, # 'W'
- 88: 163, # 'X'
- 89: 164, # 'Y'
- 90: 165, # 'Z'
- 91: 253, # '['
- 92: 253, # '\\'
- 93: 253, # ']'
- 94: 253, # '^'
- 95: 253, # '_'
- 96: 253, # '`'
- 97: 71, # 'a'
- 98: 172, # 'b'
- 99: 66, # 'c'
- 100: 173, # 'd'
- 101: 65, # 'e'
- 102: 174, # 'f'
- 103: 76, # 'g'
- 104: 175, # 'h'
- 105: 64, # 'i'
- 106: 176, # 'j'
- 107: 177, # 'k'
- 108: 77, # 'l'
- 109: 72, # 'm'
- 110: 178, # 'n'
- 111: 69, # 'o'
- 112: 67, # 'p'
- 113: 179, # 'q'
- 114: 78, # 'r'
- 115: 73, # 's'
- 116: 180, # 't'
- 117: 181, # 'u'
- 118: 79, # 'v'
- 119: 182, # 'w'
- 120: 183, # 'x'
- 121: 184, # 'y'
- 122: 185, # 'z'
- 123: 253, # '{'
- 124: 253, # '|'
- 125: 253, # '}'
- 126: 253, # '~'
- 127: 253, # '\x7f'
- 128: 191, # 'ђ'
- 129: 192, # 'Ђ'
- 130: 193, # 'ѓ'
- 131: 194, # 'Ѓ'
- 132: 68, # 'ё'
- 133: 195, # 'Ё'
- 134: 196, # 'є'
- 135: 197, # 'Є'
- 136: 198, # 'ѕ'
- 137: 199, # 'Ѕ'
- 138: 200, # 'і'
- 139: 201, # 'І'
- 140: 202, # 'ї'
- 141: 203, # 'Ї'
- 142: 204, # 'ј'
- 143: 205, # 'Ј'
- 144: 206, # 'љ'
- 145: 207, # 'Љ'
- 146: 208, # 'њ'
- 147: 209, # 'Њ'
- 148: 210, # 'ћ'
- 149: 211, # 'Ћ'
- 150: 212, # 'ќ'
- 151: 213, # 'Ќ'
- 152: 214, # 'ў'
- 153: 215, # 'Ў'
- 154: 216, # 'џ'
- 155: 217, # 'Џ'
- 156: 27, # 'ю'
- 157: 59, # 'Ю'
- 158: 54, # 'ъ'
- 159: 70, # 'Ъ'
- 160: 3, # 'а'
- 161: 37, # 'А'
- 162: 21, # 'б'
- 163: 44, # 'Б'
- 164: 28, # 'ц'
- 165: 58, # 'Ц'
- 166: 13, # 'д'
- 167: 41, # 'Д'
- 168: 2, # 'е'
- 169: 48, # 'Е'
- 170: 39, # 'ф'
- 171: 53, # 'Ф'
- 172: 19, # 'г'
- 173: 46, # 'Г'
- 174: 218, # '«'
- 175: 219, # '»'
- 176: 220, # '░'
- 177: 221, # '▒'
- 178: 222, # '▓'
- 179: 223, # '│'
- 180: 224, # '┤'
- 181: 26, # 'х'
- 182: 55, # 'Х'
- 183: 4, # 'и'
- 184: 42, # 'И'
- 185: 225, # '╣'
- 186: 226, # '║'
- 187: 227, # '╗'
- 188: 228, # '╝'
- 189: 23, # 'й'
- 190: 60, # 'Й'
- 191: 229, # '┐'
- 192: 230, # '└'
- 193: 231, # '┴'
- 194: 232, # '┬'
- 195: 233, # '├'
- 196: 234, # '─'
- 197: 235, # '┼'
- 198: 11, # 'к'
- 199: 36, # 'К'
- 200: 236, # '╚'
- 201: 237, # '╔'
- 202: 238, # '╩'
- 203: 239, # '╦'
- 204: 240, # '╠'
- 205: 241, # '═'
- 206: 242, # '╬'
- 207: 243, # '¤'
- 208: 8, # 'л'
- 209: 49, # 'Л'
- 210: 12, # 'м'
- 211: 38, # 'М'
- 212: 5, # 'н'
- 213: 31, # 'Н'
- 214: 1, # 'о'
- 215: 34, # 'О'
- 216: 15, # 'п'
- 217: 244, # '┘'
- 218: 245, # '┌'
- 219: 246, # '█'
- 220: 247, # '▄'
- 221: 35, # 'П'
- 222: 16, # 'я'
- 223: 248, # '▀'
- 224: 43, # 'Я'
- 225: 9, # 'р'
- 226: 45, # 'Р'
- 227: 7, # 'с'
- 228: 32, # 'С'
- 229: 6, # 'т'
- 230: 40, # 'Т'
- 231: 14, # 'у'
- 232: 52, # 'У'
- 233: 24, # 'ж'
- 234: 56, # 'Ж'
- 235: 10, # 'в'
- 236: 33, # 'В'
- 237: 17, # 'ь'
- 238: 61, # 'Ь'
- 239: 249, # '№'
- 240: 250, # '\xad'
- 241: 18, # 'ы'
- 242: 62, # 'Ы'
- 243: 20, # 'з'
- 244: 51, # 'З'
- 245: 25, # 'ш'
- 246: 57, # 'Ш'
- 247: 30, # 'э'
- 248: 47, # 'Э'
- 249: 29, # 'щ'
- 250: 63, # 'Щ'
- 251: 22, # 'ч'
- 252: 50, # 'Ч'
- 253: 251, # '§'
- 254: 252, # '■'
- 255: 255, # '\xa0'
+ 0: 255, # '\x00'
+ 1: 255, # '\x01'
+ 2: 255, # '\x02'
+ 3: 255, # '\x03'
+ 4: 255, # '\x04'
+ 5: 255, # '\x05'
+ 6: 255, # '\x06'
+ 7: 255, # '\x07'
+ 8: 255, # '\x08'
+ 9: 255, # '\t'
+ 10: 254, # '\n'
+ 11: 255, # '\x0b'
+ 12: 255, # '\x0c'
+ 13: 254, # '\r'
+ 14: 255, # '\x0e'
+ 15: 255, # '\x0f'
+ 16: 255, # '\x10'
+ 17: 255, # '\x11'
+ 18: 255, # '\x12'
+ 19: 255, # '\x13'
+ 20: 255, # '\x14'
+ 21: 255, # '\x15'
+ 22: 255, # '\x16'
+ 23: 255, # '\x17'
+ 24: 255, # '\x18'
+ 25: 255, # '\x19'
+ 26: 255, # '\x1a'
+ 27: 255, # '\x1b'
+ 28: 255, # '\x1c'
+ 29: 255, # '\x1d'
+ 30: 255, # '\x1e'
+ 31: 255, # '\x1f'
+ 32: 253, # ' '
+ 33: 253, # '!'
+ 34: 253, # '"'
+ 35: 253, # '#'
+ 36: 253, # '$'
+ 37: 253, # '%'
+ 38: 253, # '&'
+ 39: 253, # "'"
+ 40: 253, # '('
+ 41: 253, # ')'
+ 42: 253, # '*'
+ 43: 253, # '+'
+ 44: 253, # ','
+ 45: 253, # '-'
+ 46: 253, # '.'
+ 47: 253, # '/'
+ 48: 252, # '0'
+ 49: 252, # '1'
+ 50: 252, # '2'
+ 51: 252, # '3'
+ 52: 252, # '4'
+ 53: 252, # '5'
+ 54: 252, # '6'
+ 55: 252, # '7'
+ 56: 252, # '8'
+ 57: 252, # '9'
+ 58: 253, # ':'
+ 59: 253, # ';'
+ 60: 253, # '<'
+ 61: 253, # '='
+ 62: 253, # '>'
+ 63: 253, # '?'
+ 64: 253, # '@'
+ 65: 142, # 'A'
+ 66: 143, # 'B'
+ 67: 144, # 'C'
+ 68: 145, # 'D'
+ 69: 146, # 'E'
+ 70: 147, # 'F'
+ 71: 148, # 'G'
+ 72: 149, # 'H'
+ 73: 150, # 'I'
+ 74: 151, # 'J'
+ 75: 152, # 'K'
+ 76: 74, # 'L'
+ 77: 153, # 'M'
+ 78: 75, # 'N'
+ 79: 154, # 'O'
+ 80: 155, # 'P'
+ 81: 156, # 'Q'
+ 82: 157, # 'R'
+ 83: 158, # 'S'
+ 84: 159, # 'T'
+ 85: 160, # 'U'
+ 86: 161, # 'V'
+ 87: 162, # 'W'
+ 88: 163, # 'X'
+ 89: 164, # 'Y'
+ 90: 165, # 'Z'
+ 91: 253, # '['
+ 92: 253, # '\\'
+ 93: 253, # ']'
+ 94: 253, # '^'
+ 95: 253, # '_'
+ 96: 253, # '`'
+ 97: 71, # 'a'
+ 98: 172, # 'b'
+ 99: 66, # 'c'
+ 100: 173, # 'd'
+ 101: 65, # 'e'
+ 102: 174, # 'f'
+ 103: 76, # 'g'
+ 104: 175, # 'h'
+ 105: 64, # 'i'
+ 106: 176, # 'j'
+ 107: 177, # 'k'
+ 108: 77, # 'l'
+ 109: 72, # 'm'
+ 110: 178, # 'n'
+ 111: 69, # 'o'
+ 112: 67, # 'p'
+ 113: 179, # 'q'
+ 114: 78, # 'r'
+ 115: 73, # 's'
+ 116: 180, # 't'
+ 117: 181, # 'u'
+ 118: 79, # 'v'
+ 119: 182, # 'w'
+ 120: 183, # 'x'
+ 121: 184, # 'y'
+ 122: 185, # 'z'
+ 123: 253, # '{'
+ 124: 253, # '|'
+ 125: 253, # '}'
+ 126: 253, # '~'
+ 127: 253, # '\x7f'
+ 128: 191, # 'ђ'
+ 129: 192, # 'Ђ'
+ 130: 193, # 'ѓ'
+ 131: 194, # 'Ѓ'
+ 132: 68, # 'ё'
+ 133: 195, # 'Ё'
+ 134: 196, # 'є'
+ 135: 197, # 'Є'
+ 136: 198, # 'ѕ'
+ 137: 199, # 'Ѕ'
+ 138: 200, # 'і'
+ 139: 201, # 'І'
+ 140: 202, # 'ї'
+ 141: 203, # 'Ї'
+ 142: 204, # 'ј'
+ 143: 205, # 'Ј'
+ 144: 206, # 'љ'
+ 145: 207, # 'Љ'
+ 146: 208, # 'њ'
+ 147: 209, # 'Њ'
+ 148: 210, # 'ћ'
+ 149: 211, # 'Ћ'
+ 150: 212, # 'ќ'
+ 151: 213, # 'Ќ'
+ 152: 214, # 'ў'
+ 153: 215, # 'Ў'
+ 154: 216, # 'џ'
+ 155: 217, # 'Џ'
+ 156: 27, # 'ю'
+ 157: 59, # 'Ю'
+ 158: 54, # 'ъ'
+ 159: 70, # 'Ъ'
+ 160: 3, # 'а'
+ 161: 37, # 'А'
+ 162: 21, # 'б'
+ 163: 44, # 'Б'
+ 164: 28, # 'ц'
+ 165: 58, # 'Ц'
+ 166: 13, # 'д'
+ 167: 41, # 'Д'
+ 168: 2, # 'е'
+ 169: 48, # 'Е'
+ 170: 39, # 'ф'
+ 171: 53, # 'Ф'
+ 172: 19, # 'г'
+ 173: 46, # 'Г'
+ 174: 218, # '«'
+ 175: 219, # '»'
+ 176: 220, # '░'
+ 177: 221, # '▒'
+ 178: 222, # '▓'
+ 179: 223, # '│'
+ 180: 224, # '┤'
+ 181: 26, # 'х'
+ 182: 55, # 'Х'
+ 183: 4, # 'и'
+ 184: 42, # 'И'
+ 185: 225, # '╣'
+ 186: 226, # '║'
+ 187: 227, # '╗'
+ 188: 228, # '╝'
+ 189: 23, # 'й'
+ 190: 60, # 'Й'
+ 191: 229, # '┐'
+ 192: 230, # '└'
+ 193: 231, # '┴'
+ 194: 232, # '┬'
+ 195: 233, # '├'
+ 196: 234, # '─'
+ 197: 235, # '┼'
+ 198: 11, # 'к'
+ 199: 36, # 'К'
+ 200: 236, # '╚'
+ 201: 237, # '╔'
+ 202: 238, # '╩'
+ 203: 239, # '╦'
+ 204: 240, # '╠'
+ 205: 241, # '═'
+ 206: 242, # '╬'
+ 207: 243, # '¤'
+ 208: 8, # 'л'
+ 209: 49, # 'Л'
+ 210: 12, # 'м'
+ 211: 38, # 'М'
+ 212: 5, # 'н'
+ 213: 31, # 'Н'
+ 214: 1, # 'о'
+ 215: 34, # 'О'
+ 216: 15, # 'п'
+ 217: 244, # '┘'
+ 218: 245, # '┌'
+ 219: 246, # '█'
+ 220: 247, # '▄'
+ 221: 35, # 'П'
+ 222: 16, # 'я'
+ 223: 248, # '▀'
+ 224: 43, # 'Я'
+ 225: 9, # 'р'
+ 226: 45, # 'Р'
+ 227: 7, # 'с'
+ 228: 32, # 'С'
+ 229: 6, # 'т'
+ 230: 40, # 'Т'
+ 231: 14, # 'у'
+ 232: 52, # 'У'
+ 233: 24, # 'ж'
+ 234: 56, # 'Ж'
+ 235: 10, # 'в'
+ 236: 33, # 'В'
+ 237: 17, # 'ь'
+ 238: 61, # 'Ь'
+ 239: 249, # '№'
+ 240: 250, # '\xad'
+ 241: 18, # 'ы'
+ 242: 62, # 'Ы'
+ 243: 20, # 'з'
+ 244: 51, # 'З'
+ 245: 25, # 'ш'
+ 246: 57, # 'Ш'
+ 247: 30, # 'э'
+ 248: 47, # 'Э'
+ 249: 29, # 'щ'
+ 250: 63, # 'Щ'
+ 251: 22, # 'ч'
+ 252: 50, # 'Ч'
+ 253: 251, # '§'
+ 254: 252, # '■'
+ 255: 255, # '\xa0'
}
-IBM855_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='IBM855',
- language='Russian',
- char_to_order_map=IBM855_RUSSIAN_CHAR_TO_ORDER,
- language_model=RUSSIAN_LANG_MODEL,
- typical_positive_ratio=0.976601,
- keep_ascii_letters=False,
- alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё')
+IBM855_RUSSIAN_MODEL = SingleByteCharSetModel(
+ charset_name="IBM855",
+ language="Russian",
+ char_to_order_map=IBM855_RUSSIAN_CHAR_TO_ORDER,
+ language_model=RUSSIAN_LANG_MODEL,
+ typical_positive_ratio=0.976601,
+ keep_ascii_letters=False,
+ alphabet="ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё",
+)
KOI8_R_RUSSIAN_CHAR_TO_ORDER = {
- 0: 255, # '\x00'
- 1: 255, # '\x01'
- 2: 255, # '\x02'
- 3: 255, # '\x03'
- 4: 255, # '\x04'
- 5: 255, # '\x05'
- 6: 255, # '\x06'
- 7: 255, # '\x07'
- 8: 255, # '\x08'
- 9: 255, # '\t'
- 10: 254, # '\n'
- 11: 255, # '\x0b'
- 12: 255, # '\x0c'
- 13: 254, # '\r'
- 14: 255, # '\x0e'
- 15: 255, # '\x0f'
- 16: 255, # '\x10'
- 17: 255, # '\x11'
- 18: 255, # '\x12'
- 19: 255, # '\x13'
- 20: 255, # '\x14'
- 21: 255, # '\x15'
- 22: 255, # '\x16'
- 23: 255, # '\x17'
- 24: 255, # '\x18'
- 25: 255, # '\x19'
- 26: 255, # '\x1a'
- 27: 255, # '\x1b'
- 28: 255, # '\x1c'
- 29: 255, # '\x1d'
- 30: 255, # '\x1e'
- 31: 255, # '\x1f'
- 32: 253, # ' '
- 33: 253, # '!'
- 34: 253, # '"'
- 35: 253, # '#'
- 36: 253, # '$'
- 37: 253, # '%'
- 38: 253, # '&'
- 39: 253, # "'"
- 40: 253, # '('
- 41: 253, # ')'
- 42: 253, # '*'
- 43: 253, # '+'
- 44: 253, # ','
- 45: 253, # '-'
- 46: 253, # '.'
- 47: 253, # '/'
- 48: 252, # '0'
- 49: 252, # '1'
- 50: 252, # '2'
- 51: 252, # '3'
- 52: 252, # '4'
- 53: 252, # '5'
- 54: 252, # '6'
- 55: 252, # '7'
- 56: 252, # '8'
- 57: 252, # '9'
- 58: 253, # ':'
- 59: 253, # ';'
- 60: 253, # '<'
- 61: 253, # '='
- 62: 253, # '>'
- 63: 253, # '?'
- 64: 253, # '@'
- 65: 142, # 'A'
- 66: 143, # 'B'
- 67: 144, # 'C'
- 68: 145, # 'D'
- 69: 146, # 'E'
- 70: 147, # 'F'
- 71: 148, # 'G'
- 72: 149, # 'H'
- 73: 150, # 'I'
- 74: 151, # 'J'
- 75: 152, # 'K'
- 76: 74, # 'L'
- 77: 153, # 'M'
- 78: 75, # 'N'
- 79: 154, # 'O'
- 80: 155, # 'P'
- 81: 156, # 'Q'
- 82: 157, # 'R'
- 83: 158, # 'S'
- 84: 159, # 'T'
- 85: 160, # 'U'
- 86: 161, # 'V'
- 87: 162, # 'W'
- 88: 163, # 'X'
- 89: 164, # 'Y'
- 90: 165, # 'Z'
- 91: 253, # '['
- 92: 253, # '\\'
- 93: 253, # ']'
- 94: 253, # '^'
- 95: 253, # '_'
- 96: 253, # '`'
- 97: 71, # 'a'
- 98: 172, # 'b'
- 99: 66, # 'c'
- 100: 173, # 'd'
- 101: 65, # 'e'
- 102: 174, # 'f'
- 103: 76, # 'g'
- 104: 175, # 'h'
- 105: 64, # 'i'
- 106: 176, # 'j'
- 107: 177, # 'k'
- 108: 77, # 'l'
- 109: 72, # 'm'
- 110: 178, # 'n'
- 111: 69, # 'o'
- 112: 67, # 'p'
- 113: 179, # 'q'
- 114: 78, # 'r'
- 115: 73, # 's'
- 116: 180, # 't'
- 117: 181, # 'u'
- 118: 79, # 'v'
- 119: 182, # 'w'
- 120: 183, # 'x'
- 121: 184, # 'y'
- 122: 185, # 'z'
- 123: 253, # '{'
- 124: 253, # '|'
- 125: 253, # '}'
- 126: 253, # '~'
- 127: 253, # '\x7f'
- 128: 191, # '─'
- 129: 192, # '│'
- 130: 193, # '┌'
- 131: 194, # '┐'
- 132: 195, # '└'
- 133: 196, # '┘'
- 134: 197, # '├'
- 135: 198, # '┤'
- 136: 199, # '┬'
- 137: 200, # '┴'
- 138: 201, # '┼'
- 139: 202, # '▀'
- 140: 203, # '▄'
- 141: 204, # '█'
- 142: 205, # '▌'
- 143: 206, # '▐'
- 144: 207, # '░'
- 145: 208, # '▒'
- 146: 209, # '▓'
- 147: 210, # '⌠'
- 148: 211, # '■'
- 149: 212, # '∙'
- 150: 213, # '√'
- 151: 214, # '≈'
- 152: 215, # '≤'
- 153: 216, # '≥'
- 154: 217, # '\xa0'
- 155: 218, # '⌡'
- 156: 219, # '°'
- 157: 220, # '²'
- 158: 221, # '·'
- 159: 222, # '÷'
- 160: 223, # '═'
- 161: 224, # '║'
- 162: 225, # '╒'
- 163: 68, # 'ё'
- 164: 226, # '╓'
- 165: 227, # '╔'
- 166: 228, # '╕'
- 167: 229, # '╖'
- 168: 230, # '╗'
- 169: 231, # '╘'
- 170: 232, # '╙'
- 171: 233, # '╚'
- 172: 234, # '╛'
- 173: 235, # '╜'
- 174: 236, # '╝'
- 175: 237, # '╞'
- 176: 238, # '╟'
- 177: 239, # '╠'
- 178: 240, # '╡'
- 179: 241, # 'Ё'
- 180: 242, # '╢'
- 181: 243, # '╣'
- 182: 244, # '╤'
- 183: 245, # '╥'
- 184: 246, # '╦'
- 185: 247, # '╧'
- 186: 248, # '╨'
- 187: 249, # '╩'
- 188: 250, # '╪'
- 189: 251, # '╫'
- 190: 252, # '╬'
- 191: 253, # '©'
- 192: 27, # 'ю'
- 193: 3, # 'а'
- 194: 21, # 'б'
- 195: 28, # 'ц'
- 196: 13, # 'д'
- 197: 2, # 'е'
- 198: 39, # 'ф'
- 199: 19, # 'г'
- 200: 26, # 'х'
- 201: 4, # 'и'
- 202: 23, # 'й'
- 203: 11, # 'к'
- 204: 8, # 'л'
- 205: 12, # 'м'
- 206: 5, # 'н'
- 207: 1, # 'о'
- 208: 15, # 'п'
- 209: 16, # 'я'
- 210: 9, # 'р'
- 211: 7, # 'с'
- 212: 6, # 'т'
- 213: 14, # 'у'
- 214: 24, # 'ж'
- 215: 10, # 'в'
- 216: 17, # 'ь'
- 217: 18, # 'ы'
- 218: 20, # 'з'
- 219: 25, # 'ш'
- 220: 30, # 'э'
- 221: 29, # 'щ'
- 222: 22, # 'ч'
- 223: 54, # 'ъ'
- 224: 59, # 'Ю'
- 225: 37, # 'А'
- 226: 44, # 'Б'
- 227: 58, # 'Ц'
- 228: 41, # 'Д'
- 229: 48, # 'Е'
- 230: 53, # 'Ф'
- 231: 46, # 'Г'
- 232: 55, # 'Х'
- 233: 42, # 'И'
- 234: 60, # 'Й'
- 235: 36, # 'К'
- 236: 49, # 'Л'
- 237: 38, # 'М'
- 238: 31, # 'Н'
- 239: 34, # 'О'
- 240: 35, # 'П'
- 241: 43, # 'Я'
- 242: 45, # 'Р'
- 243: 32, # 'С'
- 244: 40, # 'Т'
- 245: 52, # 'У'
- 246: 56, # 'Ж'
- 247: 33, # 'В'
- 248: 61, # 'Ь'
- 249: 62, # 'Ы'
- 250: 51, # 'З'
- 251: 57, # 'Ш'
- 252: 47, # 'Э'
- 253: 63, # 'Щ'
- 254: 50, # 'Ч'
- 255: 70, # 'Ъ'
+ 0: 255, # '\x00'
+ 1: 255, # '\x01'
+ 2: 255, # '\x02'
+ 3: 255, # '\x03'
+ 4: 255, # '\x04'
+ 5: 255, # '\x05'
+ 6: 255, # '\x06'
+ 7: 255, # '\x07'
+ 8: 255, # '\x08'
+ 9: 255, # '\t'
+ 10: 254, # '\n'
+ 11: 255, # '\x0b'
+ 12: 255, # '\x0c'
+ 13: 254, # '\r'
+ 14: 255, # '\x0e'
+ 15: 255, # '\x0f'
+ 16: 255, # '\x10'
+ 17: 255, # '\x11'
+ 18: 255, # '\x12'
+ 19: 255, # '\x13'
+ 20: 255, # '\x14'
+ 21: 255, # '\x15'
+ 22: 255, # '\x16'
+ 23: 255, # '\x17'
+ 24: 255, # '\x18'
+ 25: 255, # '\x19'
+ 26: 255, # '\x1a'
+ 27: 255, # '\x1b'
+ 28: 255, # '\x1c'
+ 29: 255, # '\x1d'
+ 30: 255, # '\x1e'
+ 31: 255, # '\x1f'
+ 32: 253, # ' '
+ 33: 253, # '!'
+ 34: 253, # '"'
+ 35: 253, # '#'
+ 36: 253, # '$'
+ 37: 253, # '%'
+ 38: 253, # '&'
+ 39: 253, # "'"
+ 40: 253, # '('
+ 41: 253, # ')'
+ 42: 253, # '*'
+ 43: 253, # '+'
+ 44: 253, # ','
+ 45: 253, # '-'
+ 46: 253, # '.'
+ 47: 253, # '/'
+ 48: 252, # '0'
+ 49: 252, # '1'
+ 50: 252, # '2'
+ 51: 252, # '3'
+ 52: 252, # '4'
+ 53: 252, # '5'
+ 54: 252, # '6'
+ 55: 252, # '7'
+ 56: 252, # '8'
+ 57: 252, # '9'
+ 58: 253, # ':'
+ 59: 253, # ';'
+ 60: 253, # '<'
+ 61: 253, # '='
+ 62: 253, # '>'
+ 63: 253, # '?'
+ 64: 253, # '@'
+ 65: 142, # 'A'
+ 66: 143, # 'B'
+ 67: 144, # 'C'
+ 68: 145, # 'D'
+ 69: 146, # 'E'
+ 70: 147, # 'F'
+ 71: 148, # 'G'
+ 72: 149, # 'H'
+ 73: 150, # 'I'
+ 74: 151, # 'J'
+ 75: 152, # 'K'
+ 76: 74, # 'L'
+ 77: 153, # 'M'
+ 78: 75, # 'N'
+ 79: 154, # 'O'
+ 80: 155, # 'P'
+ 81: 156, # 'Q'
+ 82: 157, # 'R'
+ 83: 158, # 'S'
+ 84: 159, # 'T'
+ 85: 160, # 'U'
+ 86: 161, # 'V'
+ 87: 162, # 'W'
+ 88: 163, # 'X'
+ 89: 164, # 'Y'
+ 90: 165, # 'Z'
+ 91: 253, # '['
+ 92: 253, # '\\'
+ 93: 253, # ']'
+ 94: 253, # '^'
+ 95: 253, # '_'
+ 96: 253, # '`'
+ 97: 71, # 'a'
+ 98: 172, # 'b'
+ 99: 66, # 'c'
+ 100: 173, # 'd'
+ 101: 65, # 'e'
+ 102: 174, # 'f'
+ 103: 76, # 'g'
+ 104: 175, # 'h'
+ 105: 64, # 'i'
+ 106: 176, # 'j'
+ 107: 177, # 'k'
+ 108: 77, # 'l'
+ 109: 72, # 'm'
+ 110: 178, # 'n'
+ 111: 69, # 'o'
+ 112: 67, # 'p'
+ 113: 179, # 'q'
+ 114: 78, # 'r'
+ 115: 73, # 's'
+ 116: 180, # 't'
+ 117: 181, # 'u'
+ 118: 79, # 'v'
+ 119: 182, # 'w'
+ 120: 183, # 'x'
+ 121: 184, # 'y'
+ 122: 185, # 'z'
+ 123: 253, # '{'
+ 124: 253, # '|'
+ 125: 253, # '}'
+ 126: 253, # '~'
+ 127: 253, # '\x7f'
+ 128: 191, # '─'
+ 129: 192, # '│'
+ 130: 193, # '┌'
+ 131: 194, # '┐'
+ 132: 195, # '└'
+ 133: 196, # '┘'
+ 134: 197, # '├'
+ 135: 198, # '┤'
+ 136: 199, # '┬'
+ 137: 200, # '┴'
+ 138: 201, # '┼'
+ 139: 202, # '▀'
+ 140: 203, # '▄'
+ 141: 204, # '█'
+ 142: 205, # '▌'
+ 143: 206, # '▐'
+ 144: 207, # '░'
+ 145: 208, # '▒'
+ 146: 209, # '▓'
+ 147: 210, # '⌠'
+ 148: 211, # '■'
+ 149: 212, # '∙'
+ 150: 213, # '√'
+ 151: 214, # '≈'
+ 152: 215, # '≤'
+ 153: 216, # '≥'
+ 154: 217, # '\xa0'
+ 155: 218, # '⌡'
+ 156: 219, # '°'
+ 157: 220, # '²'
+ 158: 221, # '·'
+ 159: 222, # '÷'
+ 160: 223, # '═'
+ 161: 224, # '║'
+ 162: 225, # '╒'
+ 163: 68, # 'ё'
+ 164: 226, # '╓'
+ 165: 227, # '╔'
+ 166: 228, # '╕'
+ 167: 229, # '╖'
+ 168: 230, # '╗'
+ 169: 231, # '╘'
+ 170: 232, # '╙'
+ 171: 233, # '╚'
+ 172: 234, # '╛'
+ 173: 235, # '╜'
+ 174: 236, # '╝'
+ 175: 237, # '╞'
+ 176: 238, # '╟'
+ 177: 239, # '╠'
+ 178: 240, # '╡'
+ 179: 241, # 'Ё'
+ 180: 242, # '╢'
+ 181: 243, # '╣'
+ 182: 244, # '╤'
+ 183: 245, # '╥'
+ 184: 246, # '╦'
+ 185: 247, # '╧'
+ 186: 248, # '╨'
+ 187: 249, # '╩'
+ 188: 250, # '╪'
+ 189: 251, # '╫'
+ 190: 252, # '╬'
+ 191: 253, # '©'
+ 192: 27, # 'ю'
+ 193: 3, # 'а'
+ 194: 21, # 'б'
+ 195: 28, # 'ц'
+ 196: 13, # 'д'
+ 197: 2, # 'е'
+ 198: 39, # 'ф'
+ 199: 19, # 'г'
+ 200: 26, # 'х'
+ 201: 4, # 'и'
+ 202: 23, # 'й'
+ 203: 11, # 'к'
+ 204: 8, # 'л'
+ 205: 12, # 'м'
+ 206: 5, # 'н'
+ 207: 1, # 'о'
+ 208: 15, # 'п'
+ 209: 16, # 'я'
+ 210: 9, # 'р'
+ 211: 7, # 'с'
+ 212: 6, # 'т'
+ 213: 14, # 'у'
+ 214: 24, # 'ж'
+ 215: 10, # 'в'
+ 216: 17, # 'ь'
+ 217: 18, # 'ы'
+ 218: 20, # 'з'
+ 219: 25, # 'ш'
+ 220: 30, # 'э'
+ 221: 29, # 'щ'
+ 222: 22, # 'ч'
+ 223: 54, # 'ъ'
+ 224: 59, # 'Ю'
+ 225: 37, # 'А'
+ 226: 44, # 'Б'
+ 227: 58, # 'Ц'
+ 228: 41, # 'Д'
+ 229: 48, # 'Е'
+ 230: 53, # 'Ф'
+ 231: 46, # 'Г'
+ 232: 55, # 'Х'
+ 233: 42, # 'И'
+ 234: 60, # 'Й'
+ 235: 36, # 'К'
+ 236: 49, # 'Л'
+ 237: 38, # 'М'
+ 238: 31, # 'Н'
+ 239: 34, # 'О'
+ 240: 35, # 'П'
+ 241: 43, # 'Я'
+ 242: 45, # 'Р'
+ 243: 32, # 'С'
+ 244: 40, # 'Т'
+ 245: 52, # 'У'
+ 246: 56, # 'Ж'
+ 247: 33, # 'В'
+ 248: 61, # 'Ь'
+ 249: 62, # 'Ы'
+ 250: 51, # 'З'
+ 251: 57, # 'Ш'
+ 252: 47, # 'Э'
+ 253: 63, # 'Щ'
+ 254: 50, # 'Ч'
+ 255: 70, # 'Ъ'
}
-KOI8_R_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='KOI8-R',
- language='Russian',
- char_to_order_map=KOI8_R_RUSSIAN_CHAR_TO_ORDER,
- language_model=RUSSIAN_LANG_MODEL,
- typical_positive_ratio=0.976601,
- keep_ascii_letters=False,
- alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё')
+KOI8_R_RUSSIAN_MODEL = SingleByteCharSetModel(
+ charset_name="KOI8-R",
+ language="Russian",
+ char_to_order_map=KOI8_R_RUSSIAN_CHAR_TO_ORDER,
+ language_model=RUSSIAN_LANG_MODEL,
+ typical_positive_ratio=0.976601,
+ keep_ascii_letters=False,
+ alphabet="ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё",
+)
MACCYRILLIC_RUSSIAN_CHAR_TO_ORDER = {
- 0: 255, # '\x00'
- 1: 255, # '\x01'
- 2: 255, # '\x02'
- 3: 255, # '\x03'
- 4: 255, # '\x04'
- 5: 255, # '\x05'
- 6: 255, # '\x06'
- 7: 255, # '\x07'
- 8: 255, # '\x08'
- 9: 255, # '\t'
- 10: 254, # '\n'
- 11: 255, # '\x0b'
- 12: 255, # '\x0c'
- 13: 254, # '\r'
- 14: 255, # '\x0e'
- 15: 255, # '\x0f'
- 16: 255, # '\x10'
- 17: 255, # '\x11'
- 18: 255, # '\x12'
- 19: 255, # '\x13'
- 20: 255, # '\x14'
- 21: 255, # '\x15'
- 22: 255, # '\x16'
- 23: 255, # '\x17'
- 24: 255, # '\x18'
- 25: 255, # '\x19'
- 26: 255, # '\x1a'
- 27: 255, # '\x1b'
- 28: 255, # '\x1c'
- 29: 255, # '\x1d'
- 30: 255, # '\x1e'
- 31: 255, # '\x1f'
- 32: 253, # ' '
- 33: 253, # '!'
- 34: 253, # '"'
- 35: 253, # '#'
- 36: 253, # '$'
- 37: 253, # '%'
- 38: 253, # '&'
- 39: 253, # "'"
- 40: 253, # '('
- 41: 253, # ')'
- 42: 253, # '*'
- 43: 253, # '+'
- 44: 253, # ','
- 45: 253, # '-'
- 46: 253, # '.'
- 47: 253, # '/'
- 48: 252, # '0'
- 49: 252, # '1'
- 50: 252, # '2'
- 51: 252, # '3'
- 52: 252, # '4'
- 53: 252, # '5'
- 54: 252, # '6'
- 55: 252, # '7'
- 56: 252, # '8'
- 57: 252, # '9'
- 58: 253, # ':'
- 59: 253, # ';'
- 60: 253, # '<'
- 61: 253, # '='
- 62: 253, # '>'
- 63: 253, # '?'
- 64: 253, # '@'
- 65: 142, # 'A'
- 66: 143, # 'B'
- 67: 144, # 'C'
- 68: 145, # 'D'
- 69: 146, # 'E'
- 70: 147, # 'F'
- 71: 148, # 'G'
- 72: 149, # 'H'
- 73: 150, # 'I'
- 74: 151, # 'J'
- 75: 152, # 'K'
- 76: 74, # 'L'
- 77: 153, # 'M'
- 78: 75, # 'N'
- 79: 154, # 'O'
- 80: 155, # 'P'
- 81: 156, # 'Q'
- 82: 157, # 'R'
- 83: 158, # 'S'
- 84: 159, # 'T'
- 85: 160, # 'U'
- 86: 161, # 'V'
- 87: 162, # 'W'
- 88: 163, # 'X'
- 89: 164, # 'Y'
- 90: 165, # 'Z'
- 91: 253, # '['
- 92: 253, # '\\'
- 93: 253, # ']'
- 94: 253, # '^'
- 95: 253, # '_'
- 96: 253, # '`'
- 97: 71, # 'a'
- 98: 172, # 'b'
- 99: 66, # 'c'
- 100: 173, # 'd'
- 101: 65, # 'e'
- 102: 174, # 'f'
- 103: 76, # 'g'
- 104: 175, # 'h'
- 105: 64, # 'i'
- 106: 176, # 'j'
- 107: 177, # 'k'
- 108: 77, # 'l'
- 109: 72, # 'm'
- 110: 178, # 'n'
- 111: 69, # 'o'
- 112: 67, # 'p'
- 113: 179, # 'q'
- 114: 78, # 'r'
- 115: 73, # 's'
- 116: 180, # 't'
- 117: 181, # 'u'
- 118: 79, # 'v'
- 119: 182, # 'w'
- 120: 183, # 'x'
- 121: 184, # 'y'
- 122: 185, # 'z'
- 123: 253, # '{'
- 124: 253, # '|'
- 125: 253, # '}'
- 126: 253, # '~'
- 127: 253, # '\x7f'
- 128: 37, # 'А'
- 129: 44, # 'Б'
- 130: 33, # 'В'
- 131: 46, # 'Г'
- 132: 41, # 'Д'
- 133: 48, # 'Е'
- 134: 56, # 'Ж'
- 135: 51, # 'З'
- 136: 42, # 'И'
- 137: 60, # 'Й'
- 138: 36, # 'К'
- 139: 49, # 'Л'
- 140: 38, # 'М'
- 141: 31, # 'Н'
- 142: 34, # 'О'
- 143: 35, # 'П'
- 144: 45, # 'Р'
- 145: 32, # 'С'
- 146: 40, # 'Т'
- 147: 52, # 'У'
- 148: 53, # 'Ф'
- 149: 55, # 'Х'
- 150: 58, # 'Ц'
- 151: 50, # 'Ч'
- 152: 57, # 'Ш'
- 153: 63, # 'Щ'
- 154: 70, # 'Ъ'
- 155: 62, # 'Ы'
- 156: 61, # 'Ь'
- 157: 47, # 'Э'
- 158: 59, # 'Ю'
- 159: 43, # 'Я'
- 160: 191, # '†'
- 161: 192, # '°'
- 162: 193, # 'Ґ'
- 163: 194, # '£'
- 164: 195, # '§'
- 165: 196, # '•'
- 166: 197, # '¶'
- 167: 198, # 'І'
- 168: 199, # '®'
- 169: 200, # '©'
- 170: 201, # '™'
- 171: 202, # 'Ђ'
- 172: 203, # 'ђ'
- 173: 204, # '≠'
- 174: 205, # 'Ѓ'
- 175: 206, # 'ѓ'
- 176: 207, # '∞'
- 177: 208, # '±'
- 178: 209, # '≤'
- 179: 210, # '≥'
- 180: 211, # 'і'
- 181: 212, # 'µ'
- 182: 213, # 'ґ'
- 183: 214, # 'Ј'
- 184: 215, # 'Є'
- 185: 216, # 'є'
- 186: 217, # 'Ї'
- 187: 218, # 'ї'
- 188: 219, # 'Љ'
- 189: 220, # 'љ'
- 190: 221, # 'Њ'
- 191: 222, # 'њ'
- 192: 223, # 'ј'
- 193: 224, # 'Ѕ'
- 194: 225, # '¬'
- 195: 226, # '√'
- 196: 227, # 'ƒ'
- 197: 228, # '≈'
- 198: 229, # '∆'
- 199: 230, # '«'
- 200: 231, # '»'
- 201: 232, # '…'
- 202: 233, # '\xa0'
- 203: 234, # 'Ћ'
- 204: 235, # 'ћ'
- 205: 236, # 'Ќ'
- 206: 237, # 'ќ'
- 207: 238, # 'ѕ'
- 208: 239, # '–'
- 209: 240, # '—'
- 210: 241, # '“'
- 211: 242, # '”'
- 212: 243, # '‘'
- 213: 244, # '’'
- 214: 245, # '÷'
- 215: 246, # '„'
- 216: 247, # 'Ў'
- 217: 248, # 'ў'
- 218: 249, # 'Џ'
- 219: 250, # 'џ'
- 220: 251, # '№'
- 221: 252, # 'Ё'
- 222: 68, # 'ё'
- 223: 16, # 'я'
- 224: 3, # 'а'
- 225: 21, # 'б'
- 226: 10, # 'в'
- 227: 19, # 'г'
- 228: 13, # 'д'
- 229: 2, # 'е'
- 230: 24, # 'ж'
- 231: 20, # 'з'
- 232: 4, # 'и'
- 233: 23, # 'й'
- 234: 11, # 'к'
- 235: 8, # 'л'
- 236: 12, # 'м'
- 237: 5, # 'н'
- 238: 1, # 'о'
- 239: 15, # 'п'
- 240: 9, # 'р'
- 241: 7, # 'с'
- 242: 6, # 'т'
- 243: 14, # 'у'
- 244: 39, # 'ф'
- 245: 26, # 'х'
- 246: 28, # 'ц'
- 247: 22, # 'ч'
- 248: 25, # 'ш'
- 249: 29, # 'щ'
- 250: 54, # 'ъ'
- 251: 18, # 'ы'
- 252: 17, # 'ь'
- 253: 30, # 'э'
- 254: 27, # 'ю'
- 255: 255, # '€'
+ 0: 255, # '\x00'
+ 1: 255, # '\x01'
+ 2: 255, # '\x02'
+ 3: 255, # '\x03'
+ 4: 255, # '\x04'
+ 5: 255, # '\x05'
+ 6: 255, # '\x06'
+ 7: 255, # '\x07'
+ 8: 255, # '\x08'
+ 9: 255, # '\t'
+ 10: 254, # '\n'
+ 11: 255, # '\x0b'
+ 12: 255, # '\x0c'
+ 13: 254, # '\r'
+ 14: 255, # '\x0e'
+ 15: 255, # '\x0f'
+ 16: 255, # '\x10'
+ 17: 255, # '\x11'
+ 18: 255, # '\x12'
+ 19: 255, # '\x13'
+ 20: 255, # '\x14'
+ 21: 255, # '\x15'
+ 22: 255, # '\x16'
+ 23: 255, # '\x17'
+ 24: 255, # '\x18'
+ 25: 255, # '\x19'
+ 26: 255, # '\x1a'
+ 27: 255, # '\x1b'
+ 28: 255, # '\x1c'
+ 29: 255, # '\x1d'
+ 30: 255, # '\x1e'
+ 31: 255, # '\x1f'
+ 32: 253, # ' '
+ 33: 253, # '!'
+ 34: 253, # '"'
+ 35: 253, # '#'
+ 36: 253, # '$'
+ 37: 253, # '%'
+ 38: 253, # '&'
+ 39: 253, # "'"
+ 40: 253, # '('
+ 41: 253, # ')'
+ 42: 253, # '*'
+ 43: 253, # '+'
+ 44: 253, # ','
+ 45: 253, # '-'
+ 46: 253, # '.'
+ 47: 253, # '/'
+ 48: 252, # '0'
+ 49: 252, # '1'
+ 50: 252, # '2'
+ 51: 252, # '3'
+ 52: 252, # '4'
+ 53: 252, # '5'
+ 54: 252, # '6'
+ 55: 252, # '7'
+ 56: 252, # '8'
+ 57: 252, # '9'
+ 58: 253, # ':'
+ 59: 253, # ';'
+ 60: 253, # '<'
+ 61: 253, # '='
+ 62: 253, # '>'
+ 63: 253, # '?'
+ 64: 253, # '@'
+ 65: 142, # 'A'
+ 66: 143, # 'B'
+ 67: 144, # 'C'
+ 68: 145, # 'D'
+ 69: 146, # 'E'
+ 70: 147, # 'F'
+ 71: 148, # 'G'
+ 72: 149, # 'H'
+ 73: 150, # 'I'
+ 74: 151, # 'J'
+ 75: 152, # 'K'
+ 76: 74, # 'L'
+ 77: 153, # 'M'
+ 78: 75, # 'N'
+ 79: 154, # 'O'
+ 80: 155, # 'P'
+ 81: 156, # 'Q'
+ 82: 157, # 'R'
+ 83: 158, # 'S'
+ 84: 159, # 'T'
+ 85: 160, # 'U'
+ 86: 161, # 'V'
+ 87: 162, # 'W'
+ 88: 163, # 'X'
+ 89: 164, # 'Y'
+ 90: 165, # 'Z'
+ 91: 253, # '['
+ 92: 253, # '\\'
+ 93: 253, # ']'
+ 94: 253, # '^'
+ 95: 253, # '_'
+ 96: 253, # '`'
+ 97: 71, # 'a'
+ 98: 172, # 'b'
+ 99: 66, # 'c'
+ 100: 173, # 'd'
+ 101: 65, # 'e'
+ 102: 174, # 'f'
+ 103: 76, # 'g'
+ 104: 175, # 'h'
+ 105: 64, # 'i'
+ 106: 176, # 'j'
+ 107: 177, # 'k'
+ 108: 77, # 'l'
+ 109: 72, # 'm'
+ 110: 178, # 'n'
+ 111: 69, # 'o'
+ 112: 67, # 'p'
+ 113: 179, # 'q'
+ 114: 78, # 'r'
+ 115: 73, # 's'
+ 116: 180, # 't'
+ 117: 181, # 'u'
+ 118: 79, # 'v'
+ 119: 182, # 'w'
+ 120: 183, # 'x'
+ 121: 184, # 'y'
+ 122: 185, # 'z'
+ 123: 253, # '{'
+ 124: 253, # '|'
+ 125: 253, # '}'
+ 126: 253, # '~'
+ 127: 253, # '\x7f'
+ 128: 37, # 'А'
+ 129: 44, # 'Б'
+ 130: 33, # 'В'
+ 131: 46, # 'Г'
+ 132: 41, # 'Д'
+ 133: 48, # 'Е'
+ 134: 56, # 'Ж'
+ 135: 51, # 'З'
+ 136: 42, # 'И'
+ 137: 60, # 'Й'
+ 138: 36, # 'К'
+ 139: 49, # 'Л'
+ 140: 38, # 'М'
+ 141: 31, # 'Н'
+ 142: 34, # 'О'
+ 143: 35, # 'П'
+ 144: 45, # 'Р'
+ 145: 32, # 'С'
+ 146: 40, # 'Т'
+ 147: 52, # 'У'
+ 148: 53, # 'Ф'
+ 149: 55, # 'Х'
+ 150: 58, # 'Ц'
+ 151: 50, # 'Ч'
+ 152: 57, # 'Ш'
+ 153: 63, # 'Щ'
+ 154: 70, # 'Ъ'
+ 155: 62, # 'Ы'
+ 156: 61, # 'Ь'
+ 157: 47, # 'Э'
+ 158: 59, # 'Ю'
+ 159: 43, # 'Я'
+ 160: 191, # '†'
+ 161: 192, # '°'
+ 162: 193, # 'Ґ'
+ 163: 194, # '£'
+ 164: 195, # '§'
+ 165: 196, # '•'
+ 166: 197, # '¶'
+ 167: 198, # 'І'
+ 168: 199, # '®'
+ 169: 200, # '©'
+ 170: 201, # '™'
+ 171: 202, # 'Ђ'
+ 172: 203, # 'ђ'
+ 173: 204, # '≠'
+ 174: 205, # 'Ѓ'
+ 175: 206, # 'ѓ'
+ 176: 207, # '∞'
+ 177: 208, # '±'
+ 178: 209, # '≤'
+ 179: 210, # '≥'
+ 180: 211, # 'і'
+ 181: 212, # 'µ'
+ 182: 213, # 'ґ'
+ 183: 214, # 'Ј'
+ 184: 215, # 'Є'
+ 185: 216, # 'є'
+ 186: 217, # 'Ї'
+ 187: 218, # 'ї'
+ 188: 219, # 'Љ'
+ 189: 220, # 'љ'
+ 190: 221, # 'Њ'
+ 191: 222, # 'њ'
+ 192: 223, # 'ј'
+ 193: 224, # 'Ѕ'
+ 194: 225, # '¬'
+ 195: 226, # '√'
+ 196: 227, # 'ƒ'
+ 197: 228, # '≈'
+ 198: 229, # '∆'
+ 199: 230, # '«'
+ 200: 231, # '»'
+ 201: 232, # '…'
+ 202: 233, # '\xa0'
+ 203: 234, # 'Ћ'
+ 204: 235, # 'ћ'
+ 205: 236, # 'Ќ'
+ 206: 237, # 'ќ'
+ 207: 238, # 'ѕ'
+ 208: 239, # '–'
+ 209: 240, # '—'
+ 210: 241, # '“'
+ 211: 242, # '”'
+ 212: 243, # '‘'
+ 213: 244, # '’'
+ 214: 245, # '÷'
+ 215: 246, # '„'
+ 216: 247, # 'Ў'
+ 217: 248, # 'ў'
+ 218: 249, # 'Џ'
+ 219: 250, # 'џ'
+ 220: 251, # '№'
+ 221: 252, # 'Ё'
+ 222: 68, # 'ё'
+ 223: 16, # 'я'
+ 224: 3, # 'а'
+ 225: 21, # 'б'
+ 226: 10, # 'в'
+ 227: 19, # 'г'
+ 228: 13, # 'д'
+ 229: 2, # 'е'
+ 230: 24, # 'ж'
+ 231: 20, # 'з'
+ 232: 4, # 'и'
+ 233: 23, # 'й'
+ 234: 11, # 'к'
+ 235: 8, # 'л'
+ 236: 12, # 'м'
+ 237: 5, # 'н'
+ 238: 1, # 'о'
+ 239: 15, # 'п'
+ 240: 9, # 'р'
+ 241: 7, # 'с'
+ 242: 6, # 'т'
+ 243: 14, # 'у'
+ 244: 39, # 'ф'
+ 245: 26, # 'х'
+ 246: 28, # 'ц'
+ 247: 22, # 'ч'
+ 248: 25, # 'ш'
+ 249: 29, # 'щ'
+ 250: 54, # 'ъ'
+ 251: 18, # 'ы'
+ 252: 17, # 'ь'
+ 253: 30, # 'э'
+ 254: 27, # 'ю'
+ 255: 255, # '€'
}
-MACCYRILLIC_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='MacCyrillic',
- language='Russian',
- char_to_order_map=MACCYRILLIC_RUSSIAN_CHAR_TO_ORDER,
- language_model=RUSSIAN_LANG_MODEL,
- typical_positive_ratio=0.976601,
- keep_ascii_letters=False,
- alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё')
+MACCYRILLIC_RUSSIAN_MODEL = SingleByteCharSetModel(
+ charset_name="MacCyrillic",
+ language="Russian",
+ char_to_order_map=MACCYRILLIC_RUSSIAN_CHAR_TO_ORDER,
+ language_model=RUSSIAN_LANG_MODEL,
+ typical_positive_ratio=0.976601,
+ keep_ascii_letters=False,
+ alphabet="ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё",
+)
ISO_8859_5_RUSSIAN_CHAR_TO_ORDER = {
- 0: 255, # '\x00'
- 1: 255, # '\x01'
- 2: 255, # '\x02'
- 3: 255, # '\x03'
- 4: 255, # '\x04'
- 5: 255, # '\x05'
- 6: 255, # '\x06'
- 7: 255, # '\x07'
- 8: 255, # '\x08'
- 9: 255, # '\t'
- 10: 254, # '\n'
- 11: 255, # '\x0b'
- 12: 255, # '\x0c'
- 13: 254, # '\r'
- 14: 255, # '\x0e'
- 15: 255, # '\x0f'
- 16: 255, # '\x10'
- 17: 255, # '\x11'
- 18: 255, # '\x12'
- 19: 255, # '\x13'
- 20: 255, # '\x14'
- 21: 255, # '\x15'
- 22: 255, # '\x16'
- 23: 255, # '\x17'
- 24: 255, # '\x18'
- 25: 255, # '\x19'
- 26: 255, # '\x1a'
- 27: 255, # '\x1b'
- 28: 255, # '\x1c'
- 29: 255, # '\x1d'
- 30: 255, # '\x1e'
- 31: 255, # '\x1f'
- 32: 253, # ' '
- 33: 253, # '!'
- 34: 253, # '"'
- 35: 253, # '#'
- 36: 253, # '$'
- 37: 253, # '%'
- 38: 253, # '&'
- 39: 253, # "'"
- 40: 253, # '('
- 41: 253, # ')'
- 42: 253, # '*'
- 43: 253, # '+'
- 44: 253, # ','
- 45: 253, # '-'
- 46: 253, # '.'
- 47: 253, # '/'
- 48: 252, # '0'
- 49: 252, # '1'
- 50: 252, # '2'
- 51: 252, # '3'
- 52: 252, # '4'
- 53: 252, # '5'
- 54: 252, # '6'
- 55: 252, # '7'
- 56: 252, # '8'
- 57: 252, # '9'
- 58: 253, # ':'
- 59: 253, # ';'
- 60: 253, # '<'
- 61: 253, # '='
- 62: 253, # '>'
- 63: 253, # '?'
- 64: 253, # '@'
- 65: 142, # 'A'
- 66: 143, # 'B'
- 67: 144, # 'C'
- 68: 145, # 'D'
- 69: 146, # 'E'
- 70: 147, # 'F'
- 71: 148, # 'G'
- 72: 149, # 'H'
- 73: 150, # 'I'
- 74: 151, # 'J'
- 75: 152, # 'K'
- 76: 74, # 'L'
- 77: 153, # 'M'
- 78: 75, # 'N'
- 79: 154, # 'O'
- 80: 155, # 'P'
- 81: 156, # 'Q'
- 82: 157, # 'R'
- 83: 158, # 'S'
- 84: 159, # 'T'
- 85: 160, # 'U'
- 86: 161, # 'V'
- 87: 162, # 'W'
- 88: 163, # 'X'
- 89: 164, # 'Y'
- 90: 165, # 'Z'
- 91: 253, # '['
- 92: 253, # '\\'
- 93: 253, # ']'
- 94: 253, # '^'
- 95: 253, # '_'
- 96: 253, # '`'
- 97: 71, # 'a'
- 98: 172, # 'b'
- 99: 66, # 'c'
- 100: 173, # 'd'
- 101: 65, # 'e'
- 102: 174, # 'f'
- 103: 76, # 'g'
- 104: 175, # 'h'
- 105: 64, # 'i'
- 106: 176, # 'j'
- 107: 177, # 'k'
- 108: 77, # 'l'
- 109: 72, # 'm'
- 110: 178, # 'n'
- 111: 69, # 'o'
- 112: 67, # 'p'
- 113: 179, # 'q'
- 114: 78, # 'r'
- 115: 73, # 's'
- 116: 180, # 't'
- 117: 181, # 'u'
- 118: 79, # 'v'
- 119: 182, # 'w'
- 120: 183, # 'x'
- 121: 184, # 'y'
- 122: 185, # 'z'
- 123: 253, # '{'
- 124: 253, # '|'
- 125: 253, # '}'
- 126: 253, # '~'
- 127: 253, # '\x7f'
- 128: 191, # '\x80'
- 129: 192, # '\x81'
- 130: 193, # '\x82'
- 131: 194, # '\x83'
- 132: 195, # '\x84'
- 133: 196, # '\x85'
- 134: 197, # '\x86'
- 135: 198, # '\x87'
- 136: 199, # '\x88'
- 137: 200, # '\x89'
- 138: 201, # '\x8a'
- 139: 202, # '\x8b'
- 140: 203, # '\x8c'
- 141: 204, # '\x8d'
- 142: 205, # '\x8e'
- 143: 206, # '\x8f'
- 144: 207, # '\x90'
- 145: 208, # '\x91'
- 146: 209, # '\x92'
- 147: 210, # '\x93'
- 148: 211, # '\x94'
- 149: 212, # '\x95'
- 150: 213, # '\x96'
- 151: 214, # '\x97'
- 152: 215, # '\x98'
- 153: 216, # '\x99'
- 154: 217, # '\x9a'
- 155: 218, # '\x9b'
- 156: 219, # '\x9c'
- 157: 220, # '\x9d'
- 158: 221, # '\x9e'
- 159: 222, # '\x9f'
- 160: 223, # '\xa0'
- 161: 224, # 'Ё'
- 162: 225, # 'Ђ'
- 163: 226, # 'Ѓ'
- 164: 227, # 'Є'
- 165: 228, # 'Ѕ'
- 166: 229, # 'І'
- 167: 230, # 'Ї'
- 168: 231, # 'Ј'
- 169: 232, # 'Љ'
- 170: 233, # 'Њ'
- 171: 234, # 'Ћ'
- 172: 235, # 'Ќ'
- 173: 236, # '\xad'
- 174: 237, # 'Ў'
- 175: 238, # 'Џ'
- 176: 37, # 'А'
- 177: 44, # 'Б'
- 178: 33, # 'В'
- 179: 46, # 'Г'
- 180: 41, # 'Д'
- 181: 48, # 'Е'
- 182: 56, # 'Ж'
- 183: 51, # 'З'
- 184: 42, # 'И'
- 185: 60, # 'Й'
- 186: 36, # 'К'
- 187: 49, # 'Л'
- 188: 38, # 'М'
- 189: 31, # 'Н'
- 190: 34, # 'О'
- 191: 35, # 'П'
- 192: 45, # 'Р'
- 193: 32, # 'С'
- 194: 40, # 'Т'
- 195: 52, # 'У'
- 196: 53, # 'Ф'
- 197: 55, # 'Х'
- 198: 58, # 'Ц'
- 199: 50, # 'Ч'
- 200: 57, # 'Ш'
- 201: 63, # 'Щ'
- 202: 70, # 'Ъ'
- 203: 62, # 'Ы'
- 204: 61, # 'Ь'
- 205: 47, # 'Э'
- 206: 59, # 'Ю'
- 207: 43, # 'Я'
- 208: 3, # 'а'
- 209: 21, # 'б'
- 210: 10, # 'в'
- 211: 19, # 'г'
- 212: 13, # 'д'
- 213: 2, # 'е'
- 214: 24, # 'ж'
- 215: 20, # 'з'
- 216: 4, # 'и'
- 217: 23, # 'й'
- 218: 11, # 'к'
- 219: 8, # 'л'
- 220: 12, # 'м'
- 221: 5, # 'н'
- 222: 1, # 'о'
- 223: 15, # 'п'
- 224: 9, # 'р'
- 225: 7, # 'с'
- 226: 6, # 'т'
- 227: 14, # 'у'
- 228: 39, # 'ф'
- 229: 26, # 'х'
- 230: 28, # 'ц'
- 231: 22, # 'ч'
- 232: 25, # 'ш'
- 233: 29, # 'щ'
- 234: 54, # 'ъ'
- 235: 18, # 'ы'
- 236: 17, # 'ь'
- 237: 30, # 'э'
- 238: 27, # 'ю'
- 239: 16, # 'я'
- 240: 239, # '№'
- 241: 68, # 'ё'
- 242: 240, # 'ђ'
- 243: 241, # 'ѓ'
- 244: 242, # 'є'
- 245: 243, # 'ѕ'
- 246: 244, # 'і'
- 247: 245, # 'ї'
- 248: 246, # 'ј'
- 249: 247, # 'љ'
- 250: 248, # 'њ'
- 251: 249, # 'ћ'
- 252: 250, # 'ќ'
- 253: 251, # '§'
- 254: 252, # 'ў'
- 255: 255, # 'џ'
+ 0: 255, # '\x00'
+ 1: 255, # '\x01'
+ 2: 255, # '\x02'
+ 3: 255, # '\x03'
+ 4: 255, # '\x04'
+ 5: 255, # '\x05'
+ 6: 255, # '\x06'
+ 7: 255, # '\x07'
+ 8: 255, # '\x08'
+ 9: 255, # '\t'
+ 10: 254, # '\n'
+ 11: 255, # '\x0b'
+ 12: 255, # '\x0c'
+ 13: 254, # '\r'
+ 14: 255, # '\x0e'
+ 15: 255, # '\x0f'
+ 16: 255, # '\x10'
+ 17: 255, # '\x11'
+ 18: 255, # '\x12'
+ 19: 255, # '\x13'
+ 20: 255, # '\x14'
+ 21: 255, # '\x15'
+ 22: 255, # '\x16'
+ 23: 255, # '\x17'
+ 24: 255, # '\x18'
+ 25: 255, # '\x19'
+ 26: 255, # '\x1a'
+ 27: 255, # '\x1b'
+ 28: 255, # '\x1c'
+ 29: 255, # '\x1d'
+ 30: 255, # '\x1e'
+ 31: 255, # '\x1f'
+ 32: 253, # ' '
+ 33: 253, # '!'
+ 34: 253, # '"'
+ 35: 253, # '#'
+ 36: 253, # '$'
+ 37: 253, # '%'
+ 38: 253, # '&'
+ 39: 253, # "'"
+ 40: 253, # '('
+ 41: 253, # ')'
+ 42: 253, # '*'
+ 43: 253, # '+'
+ 44: 253, # ','
+ 45: 253, # '-'
+ 46: 253, # '.'
+ 47: 253, # '/'
+ 48: 252, # '0'
+ 49: 252, # '1'
+ 50: 252, # '2'
+ 51: 252, # '3'
+ 52: 252, # '4'
+ 53: 252, # '5'
+ 54: 252, # '6'
+ 55: 252, # '7'
+ 56: 252, # '8'
+ 57: 252, # '9'
+ 58: 253, # ':'
+ 59: 253, # ';'
+ 60: 253, # '<'
+ 61: 253, # '='
+ 62: 253, # '>'
+ 63: 253, # '?'
+ 64: 253, # '@'
+ 65: 142, # 'A'
+ 66: 143, # 'B'
+ 67: 144, # 'C'
+ 68: 145, # 'D'
+ 69: 146, # 'E'
+ 70: 147, # 'F'
+ 71: 148, # 'G'
+ 72: 149, # 'H'
+ 73: 150, # 'I'
+ 74: 151, # 'J'
+ 75: 152, # 'K'
+ 76: 74, # 'L'
+ 77: 153, # 'M'
+ 78: 75, # 'N'
+ 79: 154, # 'O'
+ 80: 155, # 'P'
+ 81: 156, # 'Q'
+ 82: 157, # 'R'
+ 83: 158, # 'S'
+ 84: 159, # 'T'
+ 85: 160, # 'U'
+ 86: 161, # 'V'
+ 87: 162, # 'W'
+ 88: 163, # 'X'
+ 89: 164, # 'Y'
+ 90: 165, # 'Z'
+ 91: 253, # '['
+ 92: 253, # '\\'
+ 93: 253, # ']'
+ 94: 253, # '^'
+ 95: 253, # '_'
+ 96: 253, # '`'
+ 97: 71, # 'a'
+ 98: 172, # 'b'
+ 99: 66, # 'c'
+ 100: 173, # 'd'
+ 101: 65, # 'e'
+ 102: 174, # 'f'
+ 103: 76, # 'g'
+ 104: 175, # 'h'
+ 105: 64, # 'i'
+ 106: 176, # 'j'
+ 107: 177, # 'k'
+ 108: 77, # 'l'
+ 109: 72, # 'm'
+ 110: 178, # 'n'
+ 111: 69, # 'o'
+ 112: 67, # 'p'
+ 113: 179, # 'q'
+ 114: 78, # 'r'
+ 115: 73, # 's'
+ 116: 180, # 't'
+ 117: 181, # 'u'
+ 118: 79, # 'v'
+ 119: 182, # 'w'
+ 120: 183, # 'x'
+ 121: 184, # 'y'
+ 122: 185, # 'z'
+ 123: 253, # '{'
+ 124: 253, # '|'
+ 125: 253, # '}'
+ 126: 253, # '~'
+ 127: 253, # '\x7f'
+ 128: 191, # '\x80'
+ 129: 192, # '\x81'
+ 130: 193, # '\x82'
+ 131: 194, # '\x83'
+ 132: 195, # '\x84'
+ 133: 196, # '\x85'
+ 134: 197, # '\x86'
+ 135: 198, # '\x87'
+ 136: 199, # '\x88'
+ 137: 200, # '\x89'
+ 138: 201, # '\x8a'
+ 139: 202, # '\x8b'
+ 140: 203, # '\x8c'
+ 141: 204, # '\x8d'
+ 142: 205, # '\x8e'
+ 143: 206, # '\x8f'
+ 144: 207, # '\x90'
+ 145: 208, # '\x91'
+ 146: 209, # '\x92'
+ 147: 210, # '\x93'
+ 148: 211, # '\x94'
+ 149: 212, # '\x95'
+ 150: 213, # '\x96'
+ 151: 214, # '\x97'
+ 152: 215, # '\x98'
+ 153: 216, # '\x99'
+ 154: 217, # '\x9a'
+ 155: 218, # '\x9b'
+ 156: 219, # '\x9c'
+ 157: 220, # '\x9d'
+ 158: 221, # '\x9e'
+ 159: 222, # '\x9f'
+ 160: 223, # '\xa0'
+ 161: 224, # 'Ё'
+ 162: 225, # 'Ђ'
+ 163: 226, # 'Ѓ'
+ 164: 227, # 'Є'
+ 165: 228, # 'Ѕ'
+ 166: 229, # 'І'
+ 167: 230, # 'Ї'
+ 168: 231, # 'Ј'
+ 169: 232, # 'Љ'
+ 170: 233, # 'Њ'
+ 171: 234, # 'Ћ'
+ 172: 235, # 'Ќ'
+ 173: 236, # '\xad'
+ 174: 237, # 'Ў'
+ 175: 238, # 'Џ'
+ 176: 37, # 'А'
+ 177: 44, # 'Б'
+ 178: 33, # 'В'
+ 179: 46, # 'Г'
+ 180: 41, # 'Д'
+ 181: 48, # 'Е'
+ 182: 56, # 'Ж'
+ 183: 51, # 'З'
+ 184: 42, # 'И'
+ 185: 60, # 'Й'
+ 186: 36, # 'К'
+ 187: 49, # 'Л'
+ 188: 38, # 'М'
+ 189: 31, # 'Н'
+ 190: 34, # 'О'
+ 191: 35, # 'П'
+ 192: 45, # 'Р'
+ 193: 32, # 'С'
+ 194: 40, # 'Т'
+ 195: 52, # 'У'
+ 196: 53, # 'Ф'
+ 197: 55, # 'Х'
+ 198: 58, # 'Ц'
+ 199: 50, # 'Ч'
+ 200: 57, # 'Ш'
+ 201: 63, # 'Щ'
+ 202: 70, # 'Ъ'
+ 203: 62, # 'Ы'
+ 204: 61, # 'Ь'
+ 205: 47, # 'Э'
+ 206: 59, # 'Ю'
+ 207: 43, # 'Я'
+ 208: 3, # 'а'
+ 209: 21, # 'б'
+ 210: 10, # 'в'
+ 211: 19, # 'г'
+ 212: 13, # 'д'
+ 213: 2, # 'е'
+ 214: 24, # 'ж'
+ 215: 20, # 'з'
+ 216: 4, # 'и'
+ 217: 23, # 'й'
+ 218: 11, # 'к'
+ 219: 8, # 'л'
+ 220: 12, # 'м'
+ 221: 5, # 'н'
+ 222: 1, # 'о'
+ 223: 15, # 'п'
+ 224: 9, # 'р'
+ 225: 7, # 'с'
+ 226: 6, # 'т'
+ 227: 14, # 'у'
+ 228: 39, # 'ф'
+ 229: 26, # 'х'
+ 230: 28, # 'ц'
+ 231: 22, # 'ч'
+ 232: 25, # 'ш'
+ 233: 29, # 'щ'
+ 234: 54, # 'ъ'
+ 235: 18, # 'ы'
+ 236: 17, # 'ь'
+ 237: 30, # 'э'
+ 238: 27, # 'ю'
+ 239: 16, # 'я'
+ 240: 239, # '№'
+ 241: 68, # 'ё'
+ 242: 240, # 'ђ'
+ 243: 241, # 'ѓ'
+ 244: 242, # 'є'
+ 245: 243, # 'ѕ'
+ 246: 244, # 'і'
+ 247: 245, # 'ї'
+ 248: 246, # 'ј'
+ 249: 247, # 'љ'
+ 250: 248, # 'њ'
+ 251: 249, # 'ћ'
+ 252: 250, # 'ќ'
+ 253: 251, # '§'
+ 254: 252, # 'ў'
+ 255: 255, # 'џ'
}
-ISO_8859_5_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='ISO-8859-5',
- language='Russian',
- char_to_order_map=ISO_8859_5_RUSSIAN_CHAR_TO_ORDER,
- language_model=RUSSIAN_LANG_MODEL,
- typical_positive_ratio=0.976601,
- keep_ascii_letters=False,
- alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё')
-
+ISO_8859_5_RUSSIAN_MODEL = SingleByteCharSetModel(
+ charset_name="ISO-8859-5",
+ language="Russian",
+ char_to_order_map=ISO_8859_5_RUSSIAN_CHAR_TO_ORDER,
+ language_model=RUSSIAN_LANG_MODEL,
+ typical_positive_ratio=0.976601,
+ keep_ascii_letters=False,
+ alphabet="ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё",
+)
diff --git a/src/pip/_vendor/chardet/langthaimodel.py b/src/pip/_vendor/chardet/langthaimodel.py
index 9a37db573..489cad930 100644
--- a/src/pip/_vendor/chardet/langthaimodel.py
+++ b/src/pip/_vendor/chardet/langthaimodel.py
@@ -1,9 +1,5 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
from pip._vendor.chardet.sbcharsetprober import SingleByteCharSetModel
-
# 3: Positive
# 2: Likely
# 1: Unlikely
@@ -4115,269 +4111,270 @@ THAI_LANG_MODEL = {
# Character Mapping Table(s):
TIS_620_THAI_CHAR_TO_ORDER = {
- 0: 255, # '\x00'
- 1: 255, # '\x01'
- 2: 255, # '\x02'
- 3: 255, # '\x03'
- 4: 255, # '\x04'
- 5: 255, # '\x05'
- 6: 255, # '\x06'
- 7: 255, # '\x07'
- 8: 255, # '\x08'
- 9: 255, # '\t'
- 10: 254, # '\n'
- 11: 255, # '\x0b'
- 12: 255, # '\x0c'
- 13: 254, # '\r'
- 14: 255, # '\x0e'
- 15: 255, # '\x0f'
- 16: 255, # '\x10'
- 17: 255, # '\x11'
- 18: 255, # '\x12'
- 19: 255, # '\x13'
- 20: 255, # '\x14'
- 21: 255, # '\x15'
- 22: 255, # '\x16'
- 23: 255, # '\x17'
- 24: 255, # '\x18'
- 25: 255, # '\x19'
- 26: 255, # '\x1a'
- 27: 255, # '\x1b'
- 28: 255, # '\x1c'
- 29: 255, # '\x1d'
- 30: 255, # '\x1e'
- 31: 255, # '\x1f'
- 32: 253, # ' '
- 33: 253, # '!'
- 34: 253, # '"'
- 35: 253, # '#'
- 36: 253, # '$'
- 37: 253, # '%'
- 38: 253, # '&'
- 39: 253, # "'"
- 40: 253, # '('
- 41: 253, # ')'
- 42: 253, # '*'
- 43: 253, # '+'
- 44: 253, # ','
- 45: 253, # '-'
- 46: 253, # '.'
- 47: 253, # '/'
- 48: 252, # '0'
- 49: 252, # '1'
- 50: 252, # '2'
- 51: 252, # '3'
- 52: 252, # '4'
- 53: 252, # '5'
- 54: 252, # '6'
- 55: 252, # '7'
- 56: 252, # '8'
- 57: 252, # '9'
- 58: 253, # ':'
- 59: 253, # ';'
- 60: 253, # '<'
- 61: 253, # '='
- 62: 253, # '>'
- 63: 253, # '?'
- 64: 253, # '@'
- 65: 182, # 'A'
- 66: 106, # 'B'
- 67: 107, # 'C'
- 68: 100, # 'D'
- 69: 183, # 'E'
- 70: 184, # 'F'
- 71: 185, # 'G'
- 72: 101, # 'H'
- 73: 94, # 'I'
- 74: 186, # 'J'
- 75: 187, # 'K'
- 76: 108, # 'L'
- 77: 109, # 'M'
- 78: 110, # 'N'
- 79: 111, # 'O'
- 80: 188, # 'P'
- 81: 189, # 'Q'
- 82: 190, # 'R'
- 83: 89, # 'S'
- 84: 95, # 'T'
- 85: 112, # 'U'
- 86: 113, # 'V'
- 87: 191, # 'W'
- 88: 192, # 'X'
- 89: 193, # 'Y'
- 90: 194, # 'Z'
- 91: 253, # '['
- 92: 253, # '\\'
- 93: 253, # ']'
- 94: 253, # '^'
- 95: 253, # '_'
- 96: 253, # '`'
- 97: 64, # 'a'
- 98: 72, # 'b'
- 99: 73, # 'c'
- 100: 114, # 'd'
- 101: 74, # 'e'
- 102: 115, # 'f'
- 103: 116, # 'g'
- 104: 102, # 'h'
- 105: 81, # 'i'
- 106: 201, # 'j'
- 107: 117, # 'k'
- 108: 90, # 'l'
- 109: 103, # 'm'
- 110: 78, # 'n'
- 111: 82, # 'o'
- 112: 96, # 'p'
- 113: 202, # 'q'
- 114: 91, # 'r'
- 115: 79, # 's'
- 116: 84, # 't'
- 117: 104, # 'u'
- 118: 105, # 'v'
- 119: 97, # 'w'
- 120: 98, # 'x'
- 121: 92, # 'y'
- 122: 203, # 'z'
- 123: 253, # '{'
- 124: 253, # '|'
- 125: 253, # '}'
- 126: 253, # '~'
- 127: 253, # '\x7f'
- 128: 209, # '\x80'
- 129: 210, # '\x81'
- 130: 211, # '\x82'
- 131: 212, # '\x83'
- 132: 213, # '\x84'
- 133: 88, # '\x85'
- 134: 214, # '\x86'
- 135: 215, # '\x87'
- 136: 216, # '\x88'
- 137: 217, # '\x89'
- 138: 218, # '\x8a'
- 139: 219, # '\x8b'
- 140: 220, # '\x8c'
- 141: 118, # '\x8d'
- 142: 221, # '\x8e'
- 143: 222, # '\x8f'
- 144: 223, # '\x90'
- 145: 224, # '\x91'
- 146: 99, # '\x92'
- 147: 85, # '\x93'
- 148: 83, # '\x94'
- 149: 225, # '\x95'
- 150: 226, # '\x96'
- 151: 227, # '\x97'
- 152: 228, # '\x98'
- 153: 229, # '\x99'
- 154: 230, # '\x9a'
- 155: 231, # '\x9b'
- 156: 232, # '\x9c'
- 157: 233, # '\x9d'
- 158: 234, # '\x9e'
- 159: 235, # '\x9f'
- 160: 236, # None
- 161: 5, # 'ก'
- 162: 30, # 'ข'
- 163: 237, # 'ฃ'
- 164: 24, # 'ค'
- 165: 238, # 'ฅ'
- 166: 75, # 'ฆ'
- 167: 8, # 'ง'
- 168: 26, # 'จ'
- 169: 52, # 'ฉ'
- 170: 34, # 'ช'
- 171: 51, # 'ซ'
- 172: 119, # 'ฌ'
- 173: 47, # 'ญ'
- 174: 58, # 'ฎ'
- 175: 57, # 'ฏ'
- 176: 49, # 'ฐ'
- 177: 53, # 'ฑ'
- 178: 55, # 'ฒ'
- 179: 43, # 'ณ'
- 180: 20, # 'ด'
- 181: 19, # 'ต'
- 182: 44, # 'ถ'
- 183: 14, # 'ท'
- 184: 48, # 'ธ'
- 185: 3, # 'น'
- 186: 17, # 'บ'
- 187: 25, # 'ป'
- 188: 39, # 'ผ'
- 189: 62, # 'ฝ'
- 190: 31, # 'พ'
- 191: 54, # 'ฟ'
- 192: 45, # 'ภ'
- 193: 9, # 'ม'
- 194: 16, # 'ย'
- 195: 2, # 'ร'
- 196: 61, # 'ฤ'
- 197: 15, # 'ล'
- 198: 239, # 'ฦ'
- 199: 12, # 'ว'
- 200: 42, # 'ศ'
- 201: 46, # 'ษ'
- 202: 18, # 'ส'
- 203: 21, # 'ห'
- 204: 76, # 'ฬ'
- 205: 4, # 'อ'
- 206: 66, # 'ฮ'
- 207: 63, # 'ฯ'
- 208: 22, # 'ะ'
- 209: 10, # 'ั'
- 210: 1, # 'า'
- 211: 36, # 'ำ'
- 212: 23, # 'ิ'
- 213: 13, # 'ี'
- 214: 40, # 'ึ'
- 215: 27, # 'ื'
- 216: 32, # 'ุ'
- 217: 35, # 'ู'
- 218: 86, # 'ฺ'
- 219: 240, # None
- 220: 241, # None
- 221: 242, # None
- 222: 243, # None
- 223: 244, # '฿'
- 224: 11, # 'เ'
- 225: 28, # 'แ'
- 226: 41, # 'โ'
- 227: 29, # 'ใ'
- 228: 33, # 'ไ'
- 229: 245, # 'ๅ'
- 230: 50, # 'ๆ'
- 231: 37, # '็'
- 232: 6, # '่'
- 233: 7, # '้'
- 234: 67, # '๊'
- 235: 77, # '๋'
- 236: 38, # '์'
- 237: 93, # 'ํ'
- 238: 246, # '๎'
- 239: 247, # '๏'
- 240: 68, # '๐'
- 241: 56, # '๑'
- 242: 59, # '๒'
- 243: 65, # '๓'
- 244: 69, # '๔'
- 245: 60, # '๕'
- 246: 70, # '๖'
- 247: 80, # '๗'
- 248: 71, # '๘'
- 249: 87, # '๙'
- 250: 248, # '๚'
- 251: 249, # '๛'
- 252: 250, # None
- 253: 251, # None
- 254: 252, # None
- 255: 253, # None
+ 0: 255, # '\x00'
+ 1: 255, # '\x01'
+ 2: 255, # '\x02'
+ 3: 255, # '\x03'
+ 4: 255, # '\x04'
+ 5: 255, # '\x05'
+ 6: 255, # '\x06'
+ 7: 255, # '\x07'
+ 8: 255, # '\x08'
+ 9: 255, # '\t'
+ 10: 254, # '\n'
+ 11: 255, # '\x0b'
+ 12: 255, # '\x0c'
+ 13: 254, # '\r'
+ 14: 255, # '\x0e'
+ 15: 255, # '\x0f'
+ 16: 255, # '\x10'
+ 17: 255, # '\x11'
+ 18: 255, # '\x12'
+ 19: 255, # '\x13'
+ 20: 255, # '\x14'
+ 21: 255, # '\x15'
+ 22: 255, # '\x16'
+ 23: 255, # '\x17'
+ 24: 255, # '\x18'
+ 25: 255, # '\x19'
+ 26: 255, # '\x1a'
+ 27: 255, # '\x1b'
+ 28: 255, # '\x1c'
+ 29: 255, # '\x1d'
+ 30: 255, # '\x1e'
+ 31: 255, # '\x1f'
+ 32: 253, # ' '
+ 33: 253, # '!'
+ 34: 253, # '"'
+ 35: 253, # '#'
+ 36: 253, # '$'
+ 37: 253, # '%'
+ 38: 253, # '&'
+ 39: 253, # "'"
+ 40: 253, # '('
+ 41: 253, # ')'
+ 42: 253, # '*'
+ 43: 253, # '+'
+ 44: 253, # ','
+ 45: 253, # '-'
+ 46: 253, # '.'
+ 47: 253, # '/'
+ 48: 252, # '0'
+ 49: 252, # '1'
+ 50: 252, # '2'
+ 51: 252, # '3'
+ 52: 252, # '4'
+ 53: 252, # '5'
+ 54: 252, # '6'
+ 55: 252, # '7'
+ 56: 252, # '8'
+ 57: 252, # '9'
+ 58: 253, # ':'
+ 59: 253, # ';'
+ 60: 253, # '<'
+ 61: 253, # '='
+ 62: 253, # '>'
+ 63: 253, # '?'
+ 64: 253, # '@'
+ 65: 182, # 'A'
+ 66: 106, # 'B'
+ 67: 107, # 'C'
+ 68: 100, # 'D'
+ 69: 183, # 'E'
+ 70: 184, # 'F'
+ 71: 185, # 'G'
+ 72: 101, # 'H'
+ 73: 94, # 'I'
+ 74: 186, # 'J'
+ 75: 187, # 'K'
+ 76: 108, # 'L'
+ 77: 109, # 'M'
+ 78: 110, # 'N'
+ 79: 111, # 'O'
+ 80: 188, # 'P'
+ 81: 189, # 'Q'
+ 82: 190, # 'R'
+ 83: 89, # 'S'
+ 84: 95, # 'T'
+ 85: 112, # 'U'
+ 86: 113, # 'V'
+ 87: 191, # 'W'
+ 88: 192, # 'X'
+ 89: 193, # 'Y'
+ 90: 194, # 'Z'
+ 91: 253, # '['
+ 92: 253, # '\\'
+ 93: 253, # ']'
+ 94: 253, # '^'
+ 95: 253, # '_'
+ 96: 253, # '`'
+ 97: 64, # 'a'
+ 98: 72, # 'b'
+ 99: 73, # 'c'
+ 100: 114, # 'd'
+ 101: 74, # 'e'
+ 102: 115, # 'f'
+ 103: 116, # 'g'
+ 104: 102, # 'h'
+ 105: 81, # 'i'
+ 106: 201, # 'j'
+ 107: 117, # 'k'
+ 108: 90, # 'l'
+ 109: 103, # 'm'
+ 110: 78, # 'n'
+ 111: 82, # 'o'
+ 112: 96, # 'p'
+ 113: 202, # 'q'
+ 114: 91, # 'r'
+ 115: 79, # 's'
+ 116: 84, # 't'
+ 117: 104, # 'u'
+ 118: 105, # 'v'
+ 119: 97, # 'w'
+ 120: 98, # 'x'
+ 121: 92, # 'y'
+ 122: 203, # 'z'
+ 123: 253, # '{'
+ 124: 253, # '|'
+ 125: 253, # '}'
+ 126: 253, # '~'
+ 127: 253, # '\x7f'
+ 128: 209, # '\x80'
+ 129: 210, # '\x81'
+ 130: 211, # '\x82'
+ 131: 212, # '\x83'
+ 132: 213, # '\x84'
+ 133: 88, # '\x85'
+ 134: 214, # '\x86'
+ 135: 215, # '\x87'
+ 136: 216, # '\x88'
+ 137: 217, # '\x89'
+ 138: 218, # '\x8a'
+ 139: 219, # '\x8b'
+ 140: 220, # '\x8c'
+ 141: 118, # '\x8d'
+ 142: 221, # '\x8e'
+ 143: 222, # '\x8f'
+ 144: 223, # '\x90'
+ 145: 224, # '\x91'
+ 146: 99, # '\x92'
+ 147: 85, # '\x93'
+ 148: 83, # '\x94'
+ 149: 225, # '\x95'
+ 150: 226, # '\x96'
+ 151: 227, # '\x97'
+ 152: 228, # '\x98'
+ 153: 229, # '\x99'
+ 154: 230, # '\x9a'
+ 155: 231, # '\x9b'
+ 156: 232, # '\x9c'
+ 157: 233, # '\x9d'
+ 158: 234, # '\x9e'
+ 159: 235, # '\x9f'
+ 160: 236, # None
+ 161: 5, # 'ก'
+ 162: 30, # 'ข'
+ 163: 237, # 'ฃ'
+ 164: 24, # 'ค'
+ 165: 238, # 'ฅ'
+ 166: 75, # 'ฆ'
+ 167: 8, # 'ง'
+ 168: 26, # 'จ'
+ 169: 52, # 'ฉ'
+ 170: 34, # 'ช'
+ 171: 51, # 'ซ'
+ 172: 119, # 'ฌ'
+ 173: 47, # 'ญ'
+ 174: 58, # 'ฎ'
+ 175: 57, # 'ฏ'
+ 176: 49, # 'ฐ'
+ 177: 53, # 'ฑ'
+ 178: 55, # 'ฒ'
+ 179: 43, # 'ณ'
+ 180: 20, # 'ด'
+ 181: 19, # 'ต'
+ 182: 44, # 'ถ'
+ 183: 14, # 'ท'
+ 184: 48, # 'ธ'
+ 185: 3, # 'น'
+ 186: 17, # 'บ'
+ 187: 25, # 'ป'
+ 188: 39, # 'ผ'
+ 189: 62, # 'ฝ'
+ 190: 31, # 'พ'
+ 191: 54, # 'ฟ'
+ 192: 45, # 'ภ'
+ 193: 9, # 'ม'
+ 194: 16, # 'ย'
+ 195: 2, # 'ร'
+ 196: 61, # 'ฤ'
+ 197: 15, # 'ล'
+ 198: 239, # 'ฦ'
+ 199: 12, # 'ว'
+ 200: 42, # 'ศ'
+ 201: 46, # 'ษ'
+ 202: 18, # 'ส'
+ 203: 21, # 'ห'
+ 204: 76, # 'ฬ'
+ 205: 4, # 'อ'
+ 206: 66, # 'ฮ'
+ 207: 63, # 'ฯ'
+ 208: 22, # 'ะ'
+ 209: 10, # 'ั'
+ 210: 1, # 'า'
+ 211: 36, # 'ำ'
+ 212: 23, # 'ิ'
+ 213: 13, # 'ี'
+ 214: 40, # 'ึ'
+ 215: 27, # 'ื'
+ 216: 32, # 'ุ'
+ 217: 35, # 'ู'
+ 218: 86, # 'ฺ'
+ 219: 240, # None
+ 220: 241, # None
+ 221: 242, # None
+ 222: 243, # None
+ 223: 244, # '฿'
+ 224: 11, # 'เ'
+ 225: 28, # 'แ'
+ 226: 41, # 'โ'
+ 227: 29, # 'ใ'
+ 228: 33, # 'ไ'
+ 229: 245, # 'ๅ'
+ 230: 50, # 'ๆ'
+ 231: 37, # '็'
+ 232: 6, # '่'
+ 233: 7, # '้'
+ 234: 67, # '๊'
+ 235: 77, # '๋'
+ 236: 38, # '์'
+ 237: 93, # 'ํ'
+ 238: 246, # '๎'
+ 239: 247, # '๏'
+ 240: 68, # '๐'
+ 241: 56, # '๑'
+ 242: 59, # '๒'
+ 243: 65, # '๓'
+ 244: 69, # '๔'
+ 245: 60, # '๕'
+ 246: 70, # '๖'
+ 247: 80, # '๗'
+ 248: 71, # '๘'
+ 249: 87, # '๙'
+ 250: 248, # '๚'
+ 251: 249, # '๛'
+ 252: 250, # None
+ 253: 251, # None
+ 254: 252, # None
+ 255: 253, # None
}
-TIS_620_THAI_MODEL = SingleByteCharSetModel(charset_name='TIS-620',
- language='Thai',
- char_to_order_map=TIS_620_THAI_CHAR_TO_ORDER,
- language_model=THAI_LANG_MODEL,
- typical_positive_ratio=0.926386,
- keep_ascii_letters=False,
- alphabet='กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛')
-
+TIS_620_THAI_MODEL = SingleByteCharSetModel(
+ charset_name="TIS-620",
+ language="Thai",
+ char_to_order_map=TIS_620_THAI_CHAR_TO_ORDER,
+ language_model=THAI_LANG_MODEL,
+ typical_positive_ratio=0.926386,
+ keep_ascii_letters=False,
+ alphabet="กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛",
+)
diff --git a/src/pip/_vendor/chardet/langturkishmodel.py b/src/pip/_vendor/chardet/langturkishmodel.py
index 43f4230ae..291857c25 100644
--- a/src/pip/_vendor/chardet/langturkishmodel.py
+++ b/src/pip/_vendor/chardet/langturkishmodel.py
@@ -1,9 +1,5 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
from pip._vendor.chardet.sbcharsetprober import SingleByteCharSetModel
-
# 3: Positive
# 2: Likely
# 1: Unlikely
@@ -4115,269 +4111,270 @@ TURKISH_LANG_MODEL = {
# Character Mapping Table(s):
ISO_8859_9_TURKISH_CHAR_TO_ORDER = {
- 0: 255, # '\x00'
- 1: 255, # '\x01'
- 2: 255, # '\x02'
- 3: 255, # '\x03'
- 4: 255, # '\x04'
- 5: 255, # '\x05'
- 6: 255, # '\x06'
- 7: 255, # '\x07'
- 8: 255, # '\x08'
- 9: 255, # '\t'
- 10: 255, # '\n'
- 11: 255, # '\x0b'
- 12: 255, # '\x0c'
- 13: 255, # '\r'
- 14: 255, # '\x0e'
- 15: 255, # '\x0f'
- 16: 255, # '\x10'
- 17: 255, # '\x11'
- 18: 255, # '\x12'
- 19: 255, # '\x13'
- 20: 255, # '\x14'
- 21: 255, # '\x15'
- 22: 255, # '\x16'
- 23: 255, # '\x17'
- 24: 255, # '\x18'
- 25: 255, # '\x19'
- 26: 255, # '\x1a'
- 27: 255, # '\x1b'
- 28: 255, # '\x1c'
- 29: 255, # '\x1d'
- 30: 255, # '\x1e'
- 31: 255, # '\x1f'
- 32: 255, # ' '
- 33: 255, # '!'
- 34: 255, # '"'
- 35: 255, # '#'
- 36: 255, # '$'
- 37: 255, # '%'
- 38: 255, # '&'
- 39: 255, # "'"
- 40: 255, # '('
- 41: 255, # ')'
- 42: 255, # '*'
- 43: 255, # '+'
- 44: 255, # ','
- 45: 255, # '-'
- 46: 255, # '.'
- 47: 255, # '/'
- 48: 255, # '0'
- 49: 255, # '1'
- 50: 255, # '2'
- 51: 255, # '3'
- 52: 255, # '4'
- 53: 255, # '5'
- 54: 255, # '6'
- 55: 255, # '7'
- 56: 255, # '8'
- 57: 255, # '9'
- 58: 255, # ':'
- 59: 255, # ';'
- 60: 255, # '<'
- 61: 255, # '='
- 62: 255, # '>'
- 63: 255, # '?'
- 64: 255, # '@'
- 65: 23, # 'A'
- 66: 37, # 'B'
- 67: 47, # 'C'
- 68: 39, # 'D'
- 69: 29, # 'E'
- 70: 52, # 'F'
- 71: 36, # 'G'
- 72: 45, # 'H'
- 73: 53, # 'I'
- 74: 60, # 'J'
- 75: 16, # 'K'
- 76: 49, # 'L'
- 77: 20, # 'M'
- 78: 46, # 'N'
- 79: 42, # 'O'
- 80: 48, # 'P'
- 81: 69, # 'Q'
- 82: 44, # 'R'
- 83: 35, # 'S'
- 84: 31, # 'T'
- 85: 51, # 'U'
- 86: 38, # 'V'
- 87: 62, # 'W'
- 88: 65, # 'X'
- 89: 43, # 'Y'
- 90: 56, # 'Z'
- 91: 255, # '['
- 92: 255, # '\\'
- 93: 255, # ']'
- 94: 255, # '^'
- 95: 255, # '_'
- 96: 255, # '`'
- 97: 1, # 'a'
- 98: 21, # 'b'
- 99: 28, # 'c'
- 100: 12, # 'd'
- 101: 2, # 'e'
- 102: 18, # 'f'
- 103: 27, # 'g'
- 104: 25, # 'h'
- 105: 3, # 'i'
- 106: 24, # 'j'
- 107: 10, # 'k'
- 108: 5, # 'l'
- 109: 13, # 'm'
- 110: 4, # 'n'
- 111: 15, # 'o'
- 112: 26, # 'p'
- 113: 64, # 'q'
- 114: 7, # 'r'
- 115: 8, # 's'
- 116: 9, # 't'
- 117: 14, # 'u'
- 118: 32, # 'v'
- 119: 57, # 'w'
- 120: 58, # 'x'
- 121: 11, # 'y'
- 122: 22, # 'z'
- 123: 255, # '{'
- 124: 255, # '|'
- 125: 255, # '}'
- 126: 255, # '~'
- 127: 255, # '\x7f'
- 128: 180, # '\x80'
- 129: 179, # '\x81'
- 130: 178, # '\x82'
- 131: 177, # '\x83'
- 132: 176, # '\x84'
- 133: 175, # '\x85'
- 134: 174, # '\x86'
- 135: 173, # '\x87'
- 136: 172, # '\x88'
- 137: 171, # '\x89'
- 138: 170, # '\x8a'
- 139: 169, # '\x8b'
- 140: 168, # '\x8c'
- 141: 167, # '\x8d'
- 142: 166, # '\x8e'
- 143: 165, # '\x8f'
- 144: 164, # '\x90'
- 145: 163, # '\x91'
- 146: 162, # '\x92'
- 147: 161, # '\x93'
- 148: 160, # '\x94'
- 149: 159, # '\x95'
- 150: 101, # '\x96'
- 151: 158, # '\x97'
- 152: 157, # '\x98'
- 153: 156, # '\x99'
- 154: 155, # '\x9a'
- 155: 154, # '\x9b'
- 156: 153, # '\x9c'
- 157: 152, # '\x9d'
- 158: 151, # '\x9e'
- 159: 106, # '\x9f'
- 160: 150, # '\xa0'
- 161: 149, # '¡'
- 162: 148, # '¢'
- 163: 147, # '£'
- 164: 146, # '¤'
- 165: 145, # '¥'
- 166: 144, # '¦'
- 167: 100, # '§'
- 168: 143, # '¨'
- 169: 142, # '©'
- 170: 141, # 'ª'
- 171: 140, # '«'
- 172: 139, # '¬'
- 173: 138, # '\xad'
- 174: 137, # '®'
- 175: 136, # '¯'
- 176: 94, # '°'
- 177: 80, # '±'
- 178: 93, # '²'
- 179: 135, # '³'
- 180: 105, # '´'
- 181: 134, # 'µ'
- 182: 133, # '¶'
- 183: 63, # '·'
- 184: 132, # '¸'
- 185: 131, # '¹'
- 186: 130, # 'º'
- 187: 129, # '»'
- 188: 128, # '¼'
- 189: 127, # '½'
- 190: 126, # '¾'
- 191: 125, # '¿'
- 192: 124, # 'À'
- 193: 104, # 'Á'
- 194: 73, # 'Â'
- 195: 99, # 'Ã'
- 196: 79, # 'Ä'
- 197: 85, # 'Å'
- 198: 123, # 'Æ'
- 199: 54, # 'Ç'
- 200: 122, # 'È'
- 201: 98, # 'É'
- 202: 92, # 'Ê'
- 203: 121, # 'Ë'
- 204: 120, # 'Ì'
- 205: 91, # 'Í'
- 206: 103, # 'Î'
- 207: 119, # 'Ï'
- 208: 68, # 'Ğ'
- 209: 118, # 'Ñ'
- 210: 117, # 'Ò'
- 211: 97, # 'Ó'
- 212: 116, # 'Ô'
- 213: 115, # 'Õ'
- 214: 50, # 'Ö'
- 215: 90, # '×'
- 216: 114, # 'Ø'
- 217: 113, # 'Ù'
- 218: 112, # 'Ú'
- 219: 111, # 'Û'
- 220: 55, # 'Ü'
- 221: 41, # 'İ'
- 222: 40, # 'Ş'
- 223: 86, # 'ß'
- 224: 89, # 'à'
- 225: 70, # 'á'
- 226: 59, # 'â'
- 227: 78, # 'ã'
- 228: 71, # 'ä'
- 229: 82, # 'å'
- 230: 88, # 'æ'
- 231: 33, # 'ç'
- 232: 77, # 'è'
- 233: 66, # 'é'
- 234: 84, # 'ê'
- 235: 83, # 'ë'
- 236: 110, # 'ì'
- 237: 75, # 'í'
- 238: 61, # 'î'
- 239: 96, # 'ï'
- 240: 30, # 'ğ'
- 241: 67, # 'ñ'
- 242: 109, # 'ò'
- 243: 74, # 'ó'
- 244: 87, # 'ô'
- 245: 102, # 'õ'
- 246: 34, # 'ö'
- 247: 95, # '÷'
- 248: 81, # 'ø'
- 249: 108, # 'ù'
- 250: 76, # 'ú'
- 251: 72, # 'û'
- 252: 17, # 'ü'
- 253: 6, # 'ı'
- 254: 19, # 'ş'
- 255: 107, # 'ÿ'
+ 0: 255, # '\x00'
+ 1: 255, # '\x01'
+ 2: 255, # '\x02'
+ 3: 255, # '\x03'
+ 4: 255, # '\x04'
+ 5: 255, # '\x05'
+ 6: 255, # '\x06'
+ 7: 255, # '\x07'
+ 8: 255, # '\x08'
+ 9: 255, # '\t'
+ 10: 255, # '\n'
+ 11: 255, # '\x0b'
+ 12: 255, # '\x0c'
+ 13: 255, # '\r'
+ 14: 255, # '\x0e'
+ 15: 255, # '\x0f'
+ 16: 255, # '\x10'
+ 17: 255, # '\x11'
+ 18: 255, # '\x12'
+ 19: 255, # '\x13'
+ 20: 255, # '\x14'
+ 21: 255, # '\x15'
+ 22: 255, # '\x16'
+ 23: 255, # '\x17'
+ 24: 255, # '\x18'
+ 25: 255, # '\x19'
+ 26: 255, # '\x1a'
+ 27: 255, # '\x1b'
+ 28: 255, # '\x1c'
+ 29: 255, # '\x1d'
+ 30: 255, # '\x1e'
+ 31: 255, # '\x1f'
+ 32: 255, # ' '
+ 33: 255, # '!'
+ 34: 255, # '"'
+ 35: 255, # '#'
+ 36: 255, # '$'
+ 37: 255, # '%'
+ 38: 255, # '&'
+ 39: 255, # "'"
+ 40: 255, # '('
+ 41: 255, # ')'
+ 42: 255, # '*'
+ 43: 255, # '+'
+ 44: 255, # ','
+ 45: 255, # '-'
+ 46: 255, # '.'
+ 47: 255, # '/'
+ 48: 255, # '0'
+ 49: 255, # '1'
+ 50: 255, # '2'
+ 51: 255, # '3'
+ 52: 255, # '4'
+ 53: 255, # '5'
+ 54: 255, # '6'
+ 55: 255, # '7'
+ 56: 255, # '8'
+ 57: 255, # '9'
+ 58: 255, # ':'
+ 59: 255, # ';'
+ 60: 255, # '<'
+ 61: 255, # '='
+ 62: 255, # '>'
+ 63: 255, # '?'
+ 64: 255, # '@'
+ 65: 23, # 'A'
+ 66: 37, # 'B'
+ 67: 47, # 'C'
+ 68: 39, # 'D'
+ 69: 29, # 'E'
+ 70: 52, # 'F'
+ 71: 36, # 'G'
+ 72: 45, # 'H'
+ 73: 53, # 'I'
+ 74: 60, # 'J'
+ 75: 16, # 'K'
+ 76: 49, # 'L'
+ 77: 20, # 'M'
+ 78: 46, # 'N'
+ 79: 42, # 'O'
+ 80: 48, # 'P'
+ 81: 69, # 'Q'
+ 82: 44, # 'R'
+ 83: 35, # 'S'
+ 84: 31, # 'T'
+ 85: 51, # 'U'
+ 86: 38, # 'V'
+ 87: 62, # 'W'
+ 88: 65, # 'X'
+ 89: 43, # 'Y'
+ 90: 56, # 'Z'
+ 91: 255, # '['
+ 92: 255, # '\\'
+ 93: 255, # ']'
+ 94: 255, # '^'
+ 95: 255, # '_'
+ 96: 255, # '`'
+ 97: 1, # 'a'
+ 98: 21, # 'b'
+ 99: 28, # 'c'
+ 100: 12, # 'd'
+ 101: 2, # 'e'
+ 102: 18, # 'f'
+ 103: 27, # 'g'
+ 104: 25, # 'h'
+ 105: 3, # 'i'
+ 106: 24, # 'j'
+ 107: 10, # 'k'
+ 108: 5, # 'l'
+ 109: 13, # 'm'
+ 110: 4, # 'n'
+ 111: 15, # 'o'
+ 112: 26, # 'p'
+ 113: 64, # 'q'
+ 114: 7, # 'r'
+ 115: 8, # 's'
+ 116: 9, # 't'
+ 117: 14, # 'u'
+ 118: 32, # 'v'
+ 119: 57, # 'w'
+ 120: 58, # 'x'
+ 121: 11, # 'y'
+ 122: 22, # 'z'
+ 123: 255, # '{'
+ 124: 255, # '|'
+ 125: 255, # '}'
+ 126: 255, # '~'
+ 127: 255, # '\x7f'
+ 128: 180, # '\x80'
+ 129: 179, # '\x81'
+ 130: 178, # '\x82'
+ 131: 177, # '\x83'
+ 132: 176, # '\x84'
+ 133: 175, # '\x85'
+ 134: 174, # '\x86'
+ 135: 173, # '\x87'
+ 136: 172, # '\x88'
+ 137: 171, # '\x89'
+ 138: 170, # '\x8a'
+ 139: 169, # '\x8b'
+ 140: 168, # '\x8c'
+ 141: 167, # '\x8d'
+ 142: 166, # '\x8e'
+ 143: 165, # '\x8f'
+ 144: 164, # '\x90'
+ 145: 163, # '\x91'
+ 146: 162, # '\x92'
+ 147: 161, # '\x93'
+ 148: 160, # '\x94'
+ 149: 159, # '\x95'
+ 150: 101, # '\x96'
+ 151: 158, # '\x97'
+ 152: 157, # '\x98'
+ 153: 156, # '\x99'
+ 154: 155, # '\x9a'
+ 155: 154, # '\x9b'
+ 156: 153, # '\x9c'
+ 157: 152, # '\x9d'
+ 158: 151, # '\x9e'
+ 159: 106, # '\x9f'
+ 160: 150, # '\xa0'
+ 161: 149, # '¡'
+ 162: 148, # '¢'
+ 163: 147, # '£'
+ 164: 146, # '¤'
+ 165: 145, # '¥'
+ 166: 144, # '¦'
+ 167: 100, # '§'
+ 168: 143, # '¨'
+ 169: 142, # '©'
+ 170: 141, # 'ª'
+ 171: 140, # '«'
+ 172: 139, # '¬'
+ 173: 138, # '\xad'
+ 174: 137, # '®'
+ 175: 136, # '¯'
+ 176: 94, # '°'
+ 177: 80, # '±'
+ 178: 93, # '²'
+ 179: 135, # '³'
+ 180: 105, # '´'
+ 181: 134, # 'µ'
+ 182: 133, # '¶'
+ 183: 63, # '·'
+ 184: 132, # '¸'
+ 185: 131, # '¹'
+ 186: 130, # 'º'
+ 187: 129, # '»'
+ 188: 128, # '¼'
+ 189: 127, # '½'
+ 190: 126, # '¾'
+ 191: 125, # '¿'
+ 192: 124, # 'À'
+ 193: 104, # 'Á'
+ 194: 73, # 'Â'
+ 195: 99, # 'Ã'
+ 196: 79, # 'Ä'
+ 197: 85, # 'Å'
+ 198: 123, # 'Æ'
+ 199: 54, # 'Ç'
+ 200: 122, # 'È'
+ 201: 98, # 'É'
+ 202: 92, # 'Ê'
+ 203: 121, # 'Ë'
+ 204: 120, # 'Ì'
+ 205: 91, # 'Í'
+ 206: 103, # 'Î'
+ 207: 119, # 'Ï'
+ 208: 68, # 'Ğ'
+ 209: 118, # 'Ñ'
+ 210: 117, # 'Ò'
+ 211: 97, # 'Ó'
+ 212: 116, # 'Ô'
+ 213: 115, # 'Õ'
+ 214: 50, # 'Ö'
+ 215: 90, # '×'
+ 216: 114, # 'Ø'
+ 217: 113, # 'Ù'
+ 218: 112, # 'Ú'
+ 219: 111, # 'Û'
+ 220: 55, # 'Ü'
+ 221: 41, # 'İ'
+ 222: 40, # 'Ş'
+ 223: 86, # 'ß'
+ 224: 89, # 'à'
+ 225: 70, # 'á'
+ 226: 59, # 'â'
+ 227: 78, # 'ã'
+ 228: 71, # 'ä'
+ 229: 82, # 'å'
+ 230: 88, # 'æ'
+ 231: 33, # 'ç'
+ 232: 77, # 'è'
+ 233: 66, # 'é'
+ 234: 84, # 'ê'
+ 235: 83, # 'ë'
+ 236: 110, # 'ì'
+ 237: 75, # 'í'
+ 238: 61, # 'î'
+ 239: 96, # 'ï'
+ 240: 30, # 'ğ'
+ 241: 67, # 'ñ'
+ 242: 109, # 'ò'
+ 243: 74, # 'ó'
+ 244: 87, # 'ô'
+ 245: 102, # 'õ'
+ 246: 34, # 'ö'
+ 247: 95, # '÷'
+ 248: 81, # 'ø'
+ 249: 108, # 'ù'
+ 250: 76, # 'ú'
+ 251: 72, # 'û'
+ 252: 17, # 'ü'
+ 253: 6, # 'ı'
+ 254: 19, # 'ş'
+ 255: 107, # 'ÿ'
}
-ISO_8859_9_TURKISH_MODEL = SingleByteCharSetModel(charset_name='ISO-8859-9',
- language='Turkish',
- char_to_order_map=ISO_8859_9_TURKISH_CHAR_TO_ORDER,
- language_model=TURKISH_LANG_MODEL,
- typical_positive_ratio=0.97029,
- keep_ascii_letters=True,
- alphabet='ABCDEFGHIJKLMNOPRSTUVYZabcdefghijklmnoprstuvyzÂÇÎÖÛÜâçîöûüĞğİıŞş')
-
+ISO_8859_9_TURKISH_MODEL = SingleByteCharSetModel(
+ charset_name="ISO-8859-9",
+ language="Turkish",
+ char_to_order_map=ISO_8859_9_TURKISH_CHAR_TO_ORDER,
+ language_model=TURKISH_LANG_MODEL,
+ typical_positive_ratio=0.97029,
+ keep_ascii_letters=True,
+ alphabet="ABCDEFGHIJKLMNOPRSTUVYZabcdefghijklmnoprstuvyzÂÇÎÖÛÜâçîöûüĞğİıŞş",
+)
diff --git a/src/pip/_vendor/chardet/latin1prober.py b/src/pip/_vendor/chardet/latin1prober.py
index 7d1e8c20f..59a01d91b 100644
--- a/src/pip/_vendor/chardet/latin1prober.py
+++ b/src/pip/_vendor/chardet/latin1prober.py
@@ -26,6 +26,8 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
+from typing import List, Union
+
from .charsetprober import CharSetProber
from .enums import ProbingState
@@ -41,6 +43,7 @@ ASV = 6 # accent small vowel
ASO = 7 # accent small other
CLASS_NUM = 8 # total classes
+# fmt: off
Latin1_CharToClass = (
OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 00 - 07
OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 08 - 0F
@@ -91,34 +94,34 @@ Latin1ClassModel = (
0, 3, 1, 3, 1, 1, 1, 3, # ASV
0, 3, 1, 3, 1, 1, 3, 3, # ASO
)
+# fmt: on
class Latin1Prober(CharSetProber):
- def __init__(self):
- super(Latin1Prober, self).__init__()
- self._last_char_class = None
- self._freq_counter = None
+ def __init__(self) -> None:
+ super().__init__()
+ self._last_char_class = OTH
+ self._freq_counter: List[int] = []
self.reset()
- def reset(self):
+ def reset(self) -> None:
self._last_char_class = OTH
self._freq_counter = [0] * FREQ_CAT_NUM
- CharSetProber.reset(self)
+ super().reset()
@property
- def charset_name(self):
+ def charset_name(self) -> str:
return "ISO-8859-1"
@property
- def language(self):
+ def language(self) -> str:
return ""
- def feed(self, byte_str):
- byte_str = self.filter_with_english_letters(byte_str)
+ def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState:
+ byte_str = self.remove_xml_tags(byte_str)
for c in byte_str:
char_class = Latin1_CharToClass[c]
- freq = Latin1ClassModel[(self._last_char_class * CLASS_NUM)
- + char_class]
+ freq = Latin1ClassModel[(self._last_char_class * CLASS_NUM) + char_class]
if freq == 0:
self._state = ProbingState.NOT_ME
break
@@ -127,19 +130,18 @@ class Latin1Prober(CharSetProber):
return self.state
- def get_confidence(self):
+ def get_confidence(self) -> float:
if self.state == ProbingState.NOT_ME:
return 0.01
total = sum(self._freq_counter)
- if total < 0.01:
- confidence = 0.0
- else:
- confidence = ((self._freq_counter[3] - self._freq_counter[1] * 20.0)
- / total)
- if confidence < 0.0:
- confidence = 0.0
+ confidence = (
+ 0.0
+ if total < 0.01
+ else (self._freq_counter[3] - self._freq_counter[1] * 20.0) / total
+ )
+ confidence = max(confidence, 0.0)
# lower the confidence of latin1 so that other more accurate
# detector can take priority.
- confidence = confidence * 0.73
+ confidence *= 0.73
return confidence
diff --git a/src/pip/_vendor/chardet/macromanprober.py b/src/pip/_vendor/chardet/macromanprober.py
new file mode 100644
index 000000000..1425d10ec
--- /dev/null
+++ b/src/pip/_vendor/chardet/macromanprober.py
@@ -0,0 +1,162 @@
+######################## BEGIN LICENSE BLOCK ########################
+# This code was modified from latin1prober.py by Rob Speer <rob@lumino.so>.
+# The Original Code is Mozilla Universal charset detector code.
+#
+# The Initial Developer of the Original Code is
+# Netscape Communications Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2001
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Rob Speer - adapt to MacRoman encoding
+# Mark Pilgrim - port to Python
+# Shy Shalom - original C code
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+# 02110-1301 USA
+######################### END LICENSE BLOCK #########################
+
+from typing import List, Union
+
+from .charsetprober import CharSetProber
+from .enums import ProbingState
+
+FREQ_CAT_NUM = 4
+
+UDF = 0 # undefined
+OTH = 1 # other
+ASC = 2 # ascii capital letter
+ASS = 3 # ascii small letter
+ACV = 4 # accent capital vowel
+ACO = 5 # accent capital other
+ASV = 6 # accent small vowel
+ASO = 7 # accent small other
+ODD = 8 # character that is unlikely to appear
+CLASS_NUM = 9 # total classes
+
+# The change from Latin1 is that we explicitly look for extended characters
+# that are infrequently-occurring symbols, and consider them to always be
+# improbable. This should let MacRoman get out of the way of more likely
+# encodings in most situations.
+
+# fmt: off
+MacRoman_CharToClass = (
+ OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 00 - 07
+ OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 08 - 0F
+ OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 10 - 17
+ OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 18 - 1F
+ OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 20 - 27
+ OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 28 - 2F
+ OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 30 - 37
+ OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 38 - 3F
+ OTH, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 40 - 47
+ ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 48 - 4F
+ ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 50 - 57
+ ASC, ASC, ASC, OTH, OTH, OTH, OTH, OTH, # 58 - 5F
+ OTH, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 60 - 67
+ ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 68 - 6F
+ ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 70 - 77
+ ASS, ASS, ASS, OTH, OTH, OTH, OTH, OTH, # 78 - 7F
+ ACV, ACV, ACO, ACV, ACO, ACV, ACV, ASV, # 80 - 87
+ ASV, ASV, ASV, ASV, ASV, ASO, ASV, ASV, # 88 - 8F
+ ASV, ASV, ASV, ASV, ASV, ASV, ASO, ASV, # 90 - 97
+ ASV, ASV, ASV, ASV, ASV, ASV, ASV, ASV, # 98 - 9F
+ OTH, OTH, OTH, OTH, OTH, OTH, OTH, ASO, # A0 - A7
+ OTH, OTH, ODD, ODD, OTH, OTH, ACV, ACV, # A8 - AF
+ OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B0 - B7
+ OTH, OTH, OTH, OTH, OTH, OTH, ASV, ASV, # B8 - BF
+ OTH, OTH, ODD, OTH, ODD, OTH, OTH, OTH, # C0 - C7
+ OTH, OTH, OTH, ACV, ACV, ACV, ACV, ASV, # C8 - CF
+ OTH, OTH, OTH, OTH, OTH, OTH, OTH, ODD, # D0 - D7
+ ASV, ACV, ODD, OTH, OTH, OTH, OTH, OTH, # D8 - DF
+ OTH, OTH, OTH, OTH, OTH, ACV, ACV, ACV, # E0 - E7
+ ACV, ACV, ACV, ACV, ACV, ACV, ACV, ACV, # E8 - EF
+ ODD, ACV, ACV, ACV, ACV, ASV, ODD, ODD, # F0 - F7
+ ODD, ODD, ODD, ODD, ODD, ODD, ODD, ODD, # F8 - FF
+)
+
+# 0 : illegal
+# 1 : very unlikely
+# 2 : normal
+# 3 : very likely
+MacRomanClassModel = (
+# UDF OTH ASC ASS ACV ACO ASV ASO ODD
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, # UDF
+ 0, 3, 3, 3, 3, 3, 3, 3, 1, # OTH
+ 0, 3, 3, 3, 3, 3, 3, 3, 1, # ASC
+ 0, 3, 3, 3, 1, 1, 3, 3, 1, # ASS
+ 0, 3, 3, 3, 1, 2, 1, 2, 1, # ACV
+ 0, 3, 3, 3, 3, 3, 3, 3, 1, # ACO
+ 0, 3, 1, 3, 1, 1, 1, 3, 1, # ASV
+ 0, 3, 1, 3, 1, 1, 3, 3, 1, # ASO
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, # ODD
+)
+# fmt: on
+
+
+class MacRomanProber(CharSetProber):
+ def __init__(self) -> None:
+ super().__init__()
+ self._last_char_class = OTH
+ self._freq_counter: List[int] = []
+ self.reset()
+
+ def reset(self) -> None:
+ self._last_char_class = OTH
+ self._freq_counter = [0] * FREQ_CAT_NUM
+
+ # express the prior that MacRoman is a somewhat rare encoding;
+ # this can be done by starting out in a slightly improbable state
+ # that must be overcome
+ self._freq_counter[2] = 10
+
+ super().reset()
+
+ @property
+ def charset_name(self) -> str:
+ return "MacRoman"
+
+ @property
+ def language(self) -> str:
+ return ""
+
+ def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState:
+ byte_str = self.remove_xml_tags(byte_str)
+ for c in byte_str:
+ char_class = MacRoman_CharToClass[c]
+ freq = MacRomanClassModel[(self._last_char_class * CLASS_NUM) + char_class]
+ if freq == 0:
+ self._state = ProbingState.NOT_ME
+ break
+ self._freq_counter[freq] += 1
+ self._last_char_class = char_class
+
+ return self.state
+
+ def get_confidence(self) -> float:
+ if self.state == ProbingState.NOT_ME:
+ return 0.01
+
+ total = sum(self._freq_counter)
+ confidence = (
+ 0.0
+ if total < 0.01
+ else (self._freq_counter[3] - self._freq_counter[1] * 20.0) / total
+ )
+ confidence = max(confidence, 0.0)
+ # lower the confidence of MacRoman so that other more accurate
+ # detector can take priority.
+ confidence *= 0.73
+ return confidence
diff --git a/src/pip/_vendor/chardet/mbcharsetprober.py b/src/pip/_vendor/chardet/mbcharsetprober.py
index 6256ecfd1..666307e8f 100644
--- a/src/pip/_vendor/chardet/mbcharsetprober.py
+++ b/src/pip/_vendor/chardet/mbcharsetprober.py
@@ -27,8 +27,12 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
+from typing import Optional, Union
+
+from .chardistribution import CharDistributionAnalysis
from .charsetprober import CharSetProber
-from .enums import ProbingState, MachineState
+from .codingstatemachine import CodingStateMachine
+from .enums import LanguageFilter, MachineState, ProbingState
class MultiByteCharSetProber(CharSetProber):
@@ -36,56 +40,56 @@ class MultiByteCharSetProber(CharSetProber):
MultiByteCharSetProber
"""
- def __init__(self, lang_filter=None):
- super(MultiByteCharSetProber, self).__init__(lang_filter=lang_filter)
- self.distribution_analyzer = None
- self.coding_sm = None
- self._last_char = [0, 0]
+ def __init__(self, lang_filter: LanguageFilter = LanguageFilter.NONE) -> None:
+ super().__init__(lang_filter=lang_filter)
+ self.distribution_analyzer: Optional[CharDistributionAnalysis] = None
+ self.coding_sm: Optional[CodingStateMachine] = None
+ self._last_char = bytearray(b"\0\0")
- def reset(self):
- super(MultiByteCharSetProber, self).reset()
+ def reset(self) -> None:
+ super().reset()
if self.coding_sm:
self.coding_sm.reset()
if self.distribution_analyzer:
self.distribution_analyzer.reset()
- self._last_char = [0, 0]
-
- @property
- def charset_name(self):
- raise NotImplementedError
+ self._last_char = bytearray(b"\0\0")
- @property
- def language(self):
- raise NotImplementedError
+ def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState:
+ assert self.coding_sm is not None
+ assert self.distribution_analyzer is not None
- def feed(self, byte_str):
- for i in range(len(byte_str)):
- coding_state = self.coding_sm.next_state(byte_str[i])
+ for i, byte in enumerate(byte_str):
+ coding_state = self.coding_sm.next_state(byte)
if coding_state == MachineState.ERROR:
- self.logger.debug('%s %s prober hit error at byte %s',
- self.charset_name, self.language, i)
+ self.logger.debug(
+ "%s %s prober hit error at byte %s",
+ self.charset_name,
+ self.language,
+ i,
+ )
self._state = ProbingState.NOT_ME
break
- elif coding_state == MachineState.ITS_ME:
+ if coding_state == MachineState.ITS_ME:
self._state = ProbingState.FOUND_IT
break
- elif coding_state == MachineState.START:
+ if coding_state == MachineState.START:
char_len = self.coding_sm.get_current_charlen()
if i == 0:
- self._last_char[1] = byte_str[0]
+ self._last_char[1] = byte
self.distribution_analyzer.feed(self._last_char, char_len)
else:
- self.distribution_analyzer.feed(byte_str[i - 1:i + 1],
- char_len)
+ self.distribution_analyzer.feed(byte_str[i - 1 : i + 1], char_len)
self._last_char[0] = byte_str[-1]
if self.state == ProbingState.DETECTING:
- if (self.distribution_analyzer.got_enough_data() and
- (self.get_confidence() > self.SHORTCUT_THRESHOLD)):
+ if self.distribution_analyzer.got_enough_data() and (
+ self.get_confidence() > self.SHORTCUT_THRESHOLD
+ ):
self._state = ProbingState.FOUND_IT
return self.state
- def get_confidence(self):
+ def get_confidence(self) -> float:
+ assert self.distribution_analyzer is not None
return self.distribution_analyzer.get_confidence()
diff --git a/src/pip/_vendor/chardet/mbcsgroupprober.py b/src/pip/_vendor/chardet/mbcsgroupprober.py
index 530abe75e..6cb9cc7b3 100644
--- a/src/pip/_vendor/chardet/mbcsgroupprober.py
+++ b/src/pip/_vendor/chardet/mbcsgroupprober.py
@@ -27,20 +27,22 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
+from .big5prober import Big5Prober
from .charsetgroupprober import CharSetGroupProber
-from .utf8prober import UTF8Prober
-from .sjisprober import SJISProber
+from .cp949prober import CP949Prober
+from .enums import LanguageFilter
from .eucjpprober import EUCJPProber
-from .gb2312prober import GB2312Prober
from .euckrprober import EUCKRProber
-from .cp949prober import CP949Prober
-from .big5prober import Big5Prober
from .euctwprober import EUCTWProber
+from .gb2312prober import GB2312Prober
+from .johabprober import JOHABProber
+from .sjisprober import SJISProber
+from .utf8prober import UTF8Prober
class MBCSGroupProber(CharSetGroupProber):
- def __init__(self, lang_filter=None):
- super(MBCSGroupProber, self).__init__(lang_filter=lang_filter)
+ def __init__(self, lang_filter: LanguageFilter = LanguageFilter.NONE) -> None:
+ super().__init__(lang_filter=lang_filter)
self.probers = [
UTF8Prober(),
SJISProber(),
@@ -49,6 +51,7 @@ class MBCSGroupProber(CharSetGroupProber):
EUCKRProber(),
CP949Prober(),
Big5Prober(),
- EUCTWProber()
+ EUCTWProber(),
+ JOHABProber(),
]
self.reset()
diff --git a/src/pip/_vendor/chardet/mbcssm.py b/src/pip/_vendor/chardet/mbcssm.py
index 8360d0f28..7bbe97e66 100644
--- a/src/pip/_vendor/chardet/mbcssm.py
+++ b/src/pip/_vendor/chardet/mbcssm.py
@@ -25,43 +25,45 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
+from .codingstatemachinedict import CodingStateMachineDict
from .enums import MachineState
# BIG5
+# fmt: off
BIG5_CLS = (
- 1,1,1,1,1,1,1,1, # 00 - 07 #allow 0x00 as legal value
- 1,1,1,1,1,1,0,0, # 08 - 0f
- 1,1,1,1,1,1,1,1, # 10 - 17
- 1,1,1,0,1,1,1,1, # 18 - 1f
- 1,1,1,1,1,1,1,1, # 20 - 27
- 1,1,1,1,1,1,1,1, # 28 - 2f
- 1,1,1,1,1,1,1,1, # 30 - 37
- 1,1,1,1,1,1,1,1, # 38 - 3f
- 2,2,2,2,2,2,2,2, # 40 - 47
- 2,2,2,2,2,2,2,2, # 48 - 4f
- 2,2,2,2,2,2,2,2, # 50 - 57
- 2,2,2,2,2,2,2,2, # 58 - 5f
- 2,2,2,2,2,2,2,2, # 60 - 67
- 2,2,2,2,2,2,2,2, # 68 - 6f
- 2,2,2,2,2,2,2,2, # 70 - 77
- 2,2,2,2,2,2,2,1, # 78 - 7f
- 4,4,4,4,4,4,4,4, # 80 - 87
- 4,4,4,4,4,4,4,4, # 88 - 8f
- 4,4,4,4,4,4,4,4, # 90 - 97
- 4,4,4,4,4,4,4,4, # 98 - 9f
- 4,3,3,3,3,3,3,3, # a0 - a7
- 3,3,3,3,3,3,3,3, # a8 - af
- 3,3,3,3,3,3,3,3, # b0 - b7
- 3,3,3,3,3,3,3,3, # b8 - bf
- 3,3,3,3,3,3,3,3, # c0 - c7
- 3,3,3,3,3,3,3,3, # c8 - cf
- 3,3,3,3,3,3,3,3, # d0 - d7
- 3,3,3,3,3,3,3,3, # d8 - df
- 3,3,3,3,3,3,3,3, # e0 - e7
- 3,3,3,3,3,3,3,3, # e8 - ef
- 3,3,3,3,3,3,3,3, # f0 - f7
- 3,3,3,3,3,3,3,0 # f8 - ff
+ 1, 1, 1, 1, 1, 1, 1, 1, # 00 - 07 #allow 0x00 as legal value
+ 1, 1, 1, 1, 1, 1, 0, 0, # 08 - 0f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 10 - 17
+ 1, 1, 1, 0, 1, 1, 1, 1, # 18 - 1f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 20 - 27
+ 1, 1, 1, 1, 1, 1, 1, 1, # 28 - 2f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 30 - 37
+ 1, 1, 1, 1, 1, 1, 1, 1, # 38 - 3f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 40 - 47
+ 2, 2, 2, 2, 2, 2, 2, 2, # 48 - 4f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 50 - 57
+ 2, 2, 2, 2, 2, 2, 2, 2, # 58 - 5f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 60 - 67
+ 2, 2, 2, 2, 2, 2, 2, 2, # 68 - 6f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 70 - 77
+ 2, 2, 2, 2, 2, 2, 2, 1, # 78 - 7f
+ 4, 4, 4, 4, 4, 4, 4, 4, # 80 - 87
+ 4, 4, 4, 4, 4, 4, 4, 4, # 88 - 8f
+ 4, 4, 4, 4, 4, 4, 4, 4, # 90 - 97
+ 4, 4, 4, 4, 4, 4, 4, 4, # 98 - 9f
+ 4, 3, 3, 3, 3, 3, 3, 3, # a0 - a7
+ 3, 3, 3, 3, 3, 3, 3, 3, # a8 - af
+ 3, 3, 3, 3, 3, 3, 3, 3, # b0 - b7
+ 3, 3, 3, 3, 3, 3, 3, 3, # b8 - bf
+ 3, 3, 3, 3, 3, 3, 3, 3, # c0 - c7
+ 3, 3, 3, 3, 3, 3, 3, 3, # c8 - cf
+ 3, 3, 3, 3, 3, 3, 3, 3, # d0 - d7
+ 3, 3, 3, 3, 3, 3, 3, 3, # d8 - df
+ 3, 3, 3, 3, 3, 3, 3, 3, # e0 - e7
+ 3, 3, 3, 3, 3, 3, 3, 3, # e8 - ef
+ 3, 3, 3, 3, 3, 3, 3, 3, # f0 - f7
+ 3, 3, 3, 3, 3, 3, 3, 0 # f8 - ff
)
BIG5_ST = (
@@ -69,34 +71,37 @@ BIG5_ST = (
MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,#08-0f
MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START#10-17
)
+# fmt: on
BIG5_CHAR_LEN_TABLE = (0, 1, 1, 2, 0)
-BIG5_SM_MODEL = {'class_table': BIG5_CLS,
- 'class_factor': 5,
- 'state_table': BIG5_ST,
- 'char_len_table': BIG5_CHAR_LEN_TABLE,
- 'name': 'Big5'}
+BIG5_SM_MODEL: CodingStateMachineDict = {
+ "class_table": BIG5_CLS,
+ "class_factor": 5,
+ "state_table": BIG5_ST,
+ "char_len_table": BIG5_CHAR_LEN_TABLE,
+ "name": "Big5",
+}
# CP949
-
+# fmt: off
CP949_CLS = (
- 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,0,0, # 00 - 0f
- 1,1,1,1,1,1,1,1, 1,1,1,0,1,1,1,1, # 10 - 1f
- 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, # 20 - 2f
- 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, # 30 - 3f
- 1,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, # 40 - 4f
- 4,4,5,5,5,5,5,5, 5,5,5,1,1,1,1,1, # 50 - 5f
- 1,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5, # 60 - 6f
- 5,5,5,5,5,5,5,5, 5,5,5,1,1,1,1,1, # 70 - 7f
- 0,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, # 80 - 8f
- 6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, # 90 - 9f
- 6,7,7,7,7,7,7,7, 7,7,7,7,7,8,8,8, # a0 - af
- 7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7, # b0 - bf
- 7,7,7,7,7,7,9,2, 2,3,2,2,2,2,2,2, # c0 - cf
- 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, # d0 - df
- 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, # e0 - ef
- 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,0, # f0 - ff
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, # 00 - 0f
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, # 10 - 1f
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 20 - 2f
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # 30 - 3f
+ 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, # 40 - 4f
+ 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, # 50 - 5f
+ 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, # 60 - 6f
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, # 70 - 7f
+ 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, # 80 - 8f
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, # 90 - 9f
+ 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, # a0 - af
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, # b0 - bf
+ 7, 7, 7, 7, 7, 7, 9, 2, 2, 3, 2, 2, 2, 2, 2, 2, # c0 - cf
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, # d0 - df
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, # e0 - ef
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, # f0 - ff
)
CP949_ST = (
@@ -109,50 +114,53 @@ CP949_ST = (
MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, # 5
MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START, # 6
)
+# fmt: on
CP949_CHAR_LEN_TABLE = (0, 1, 2, 0, 1, 1, 2, 2, 0, 2)
-CP949_SM_MODEL = {'class_table': CP949_CLS,
- 'class_factor': 10,
- 'state_table': CP949_ST,
- 'char_len_table': CP949_CHAR_LEN_TABLE,
- 'name': 'CP949'}
+CP949_SM_MODEL: CodingStateMachineDict = {
+ "class_table": CP949_CLS,
+ "class_factor": 10,
+ "state_table": CP949_ST,
+ "char_len_table": CP949_CHAR_LEN_TABLE,
+ "name": "CP949",
+}
# EUC-JP
-
+# fmt: off
EUCJP_CLS = (
- 4,4,4,4,4,4,4,4, # 00 - 07
- 4,4,4,4,4,4,5,5, # 08 - 0f
- 4,4,4,4,4,4,4,4, # 10 - 17
- 4,4,4,5,4,4,4,4, # 18 - 1f
- 4,4,4,4,4,4,4,4, # 20 - 27
- 4,4,4,4,4,4,4,4, # 28 - 2f
- 4,4,4,4,4,4,4,4, # 30 - 37
- 4,4,4,4,4,4,4,4, # 38 - 3f
- 4,4,4,4,4,4,4,4, # 40 - 47
- 4,4,4,4,4,4,4,4, # 48 - 4f
- 4,4,4,4,4,4,4,4, # 50 - 57
- 4,4,4,4,4,4,4,4, # 58 - 5f
- 4,4,4,4,4,4,4,4, # 60 - 67
- 4,4,4,4,4,4,4,4, # 68 - 6f
- 4,4,4,4,4,4,4,4, # 70 - 77
- 4,4,4,4,4,4,4,4, # 78 - 7f
- 5,5,5,5,5,5,5,5, # 80 - 87
- 5,5,5,5,5,5,1,3, # 88 - 8f
- 5,5,5,5,5,5,5,5, # 90 - 97
- 5,5,5,5,5,5,5,5, # 98 - 9f
- 5,2,2,2,2,2,2,2, # a0 - a7
- 2,2,2,2,2,2,2,2, # a8 - af
- 2,2,2,2,2,2,2,2, # b0 - b7
- 2,2,2,2,2,2,2,2, # b8 - bf
- 2,2,2,2,2,2,2,2, # c0 - c7
- 2,2,2,2,2,2,2,2, # c8 - cf
- 2,2,2,2,2,2,2,2, # d0 - d7
- 2,2,2,2,2,2,2,2, # d8 - df
- 0,0,0,0,0,0,0,0, # e0 - e7
- 0,0,0,0,0,0,0,0, # e8 - ef
- 0,0,0,0,0,0,0,0, # f0 - f7
- 0,0,0,0,0,0,0,5 # f8 - ff
+ 4, 4, 4, 4, 4, 4, 4, 4, # 00 - 07
+ 4, 4, 4, 4, 4, 4, 5, 5, # 08 - 0f
+ 4, 4, 4, 4, 4, 4, 4, 4, # 10 - 17
+ 4, 4, 4, 5, 4, 4, 4, 4, # 18 - 1f
+ 4, 4, 4, 4, 4, 4, 4, 4, # 20 - 27
+ 4, 4, 4, 4, 4, 4, 4, 4, # 28 - 2f
+ 4, 4, 4, 4, 4, 4, 4, 4, # 30 - 37
+ 4, 4, 4, 4, 4, 4, 4, 4, # 38 - 3f
+ 4, 4, 4, 4, 4, 4, 4, 4, # 40 - 47
+ 4, 4, 4, 4, 4, 4, 4, 4, # 48 - 4f
+ 4, 4, 4, 4, 4, 4, 4, 4, # 50 - 57
+ 4, 4, 4, 4, 4, 4, 4, 4, # 58 - 5f
+ 4, 4, 4, 4, 4, 4, 4, 4, # 60 - 67
+ 4, 4, 4, 4, 4, 4, 4, 4, # 68 - 6f
+ 4, 4, 4, 4, 4, 4, 4, 4, # 70 - 77
+ 4, 4, 4, 4, 4, 4, 4, 4, # 78 - 7f
+ 5, 5, 5, 5, 5, 5, 5, 5, # 80 - 87
+ 5, 5, 5, 5, 5, 5, 1, 3, # 88 - 8f
+ 5, 5, 5, 5, 5, 5, 5, 5, # 90 - 97
+ 5, 5, 5, 5, 5, 5, 5, 5, # 98 - 9f
+ 5, 2, 2, 2, 2, 2, 2, 2, # a0 - a7
+ 2, 2, 2, 2, 2, 2, 2, 2, # a8 - af
+ 2, 2, 2, 2, 2, 2, 2, 2, # b0 - b7
+ 2, 2, 2, 2, 2, 2, 2, 2, # b8 - bf
+ 2, 2, 2, 2, 2, 2, 2, 2, # c0 - c7
+ 2, 2, 2, 2, 2, 2, 2, 2, # c8 - cf
+ 2, 2, 2, 2, 2, 2, 2, 2, # d0 - d7
+ 2, 2, 2, 2, 2, 2, 2, 2, # d8 - df
+ 0, 0, 0, 0, 0, 0, 0, 0, # e0 - e7
+ 0, 0, 0, 0, 0, 0, 0, 0, # e8 - ef
+ 0, 0, 0, 0, 0, 0, 0, 0, # f0 - f7
+ 0, 0, 0, 0, 0, 0, 0, 5 # f8 - ff
)
EUCJP_ST = (
@@ -162,100 +170,163 @@ EUCJP_ST = (
MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 3,MachineState.ERROR,#18-1f
3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START#20-27
)
+# fmt: on
EUCJP_CHAR_LEN_TABLE = (2, 2, 2, 3, 1, 0)
-EUCJP_SM_MODEL = {'class_table': EUCJP_CLS,
- 'class_factor': 6,
- 'state_table': EUCJP_ST,
- 'char_len_table': EUCJP_CHAR_LEN_TABLE,
- 'name': 'EUC-JP'}
+EUCJP_SM_MODEL: CodingStateMachineDict = {
+ "class_table": EUCJP_CLS,
+ "class_factor": 6,
+ "state_table": EUCJP_ST,
+ "char_len_table": EUCJP_CHAR_LEN_TABLE,
+ "name": "EUC-JP",
+}
# EUC-KR
-
+# fmt: off
EUCKR_CLS = (
- 1,1,1,1,1,1,1,1, # 00 - 07
- 1,1,1,1,1,1,0,0, # 08 - 0f
- 1,1,1,1,1,1,1,1, # 10 - 17
- 1,1,1,0,1,1,1,1, # 18 - 1f
- 1,1,1,1,1,1,1,1, # 20 - 27
- 1,1,1,1,1,1,1,1, # 28 - 2f
- 1,1,1,1,1,1,1,1, # 30 - 37
- 1,1,1,1,1,1,1,1, # 38 - 3f
- 1,1,1,1,1,1,1,1, # 40 - 47
+ 1, 1, 1, 1, 1, 1, 1, 1, # 00 - 07
+ 1, 1, 1, 1, 1, 1, 0, 0, # 08 - 0f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 10 - 17
+ 1, 1, 1, 0, 1, 1, 1, 1, # 18 - 1f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 20 - 27
+ 1, 1, 1, 1, 1, 1, 1, 1, # 28 - 2f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 30 - 37
+ 1, 1, 1, 1, 1, 1, 1, 1, # 38 - 3f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 40 - 47
+ 1, 1, 1, 1, 1, 1, 1, 1, # 48 - 4f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 50 - 57
+ 1, 1, 1, 1, 1, 1, 1, 1, # 58 - 5f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 60 - 67
+ 1, 1, 1, 1, 1, 1, 1, 1, # 68 - 6f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 70 - 77
+ 1, 1, 1, 1, 1, 1, 1, 1, # 78 - 7f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 80 - 87
+ 0, 0, 0, 0, 0, 0, 0, 0, # 88 - 8f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 90 - 97
+ 0, 0, 0, 0, 0, 0, 0, 0, # 98 - 9f
+ 0, 2, 2, 2, 2, 2, 2, 2, # a0 - a7
+ 2, 2, 2, 2, 2, 3, 3, 3, # a8 - af
+ 2, 2, 2, 2, 2, 2, 2, 2, # b0 - b7
+ 2, 2, 2, 2, 2, 2, 2, 2, # b8 - bf
+ 2, 2, 2, 2, 2, 2, 2, 2, # c0 - c7
+ 2, 3, 2, 2, 2, 2, 2, 2, # c8 - cf
+ 2, 2, 2, 2, 2, 2, 2, 2, # d0 - d7
+ 2, 2, 2, 2, 2, 2, 2, 2, # d8 - df
+ 2, 2, 2, 2, 2, 2, 2, 2, # e0 - e7
+ 2, 2, 2, 2, 2, 2, 2, 2, # e8 - ef
+ 2, 2, 2, 2, 2, 2, 2, 2, # f0 - f7
+ 2, 2, 2, 2, 2, 2, 2, 0 # f8 - ff
+)
+
+EUCKR_ST = (
+ MachineState.ERROR,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07
+ MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START #08-0f
+)
+# fmt: on
+
+EUCKR_CHAR_LEN_TABLE = (0, 1, 2, 0)
+
+EUCKR_SM_MODEL: CodingStateMachineDict = {
+ "class_table": EUCKR_CLS,
+ "class_factor": 4,
+ "state_table": EUCKR_ST,
+ "char_len_table": EUCKR_CHAR_LEN_TABLE,
+ "name": "EUC-KR",
+}
+
+# JOHAB
+# fmt: off
+JOHAB_CLS = (
+ 4,4,4,4,4,4,4,4, # 00 - 07
+ 4,4,4,4,4,4,0,0, # 08 - 0f
+ 4,4,4,4,4,4,4,4, # 10 - 17
+ 4,4,4,0,4,4,4,4, # 18 - 1f
+ 4,4,4,4,4,4,4,4, # 20 - 27
+ 4,4,4,4,4,4,4,4, # 28 - 2f
+ 4,3,3,3,3,3,3,3, # 30 - 37
+ 3,3,3,3,3,3,3,3, # 38 - 3f
+ 3,1,1,1,1,1,1,1, # 40 - 47
1,1,1,1,1,1,1,1, # 48 - 4f
1,1,1,1,1,1,1,1, # 50 - 57
1,1,1,1,1,1,1,1, # 58 - 5f
1,1,1,1,1,1,1,1, # 60 - 67
1,1,1,1,1,1,1,1, # 68 - 6f
1,1,1,1,1,1,1,1, # 70 - 77
- 1,1,1,1,1,1,1,1, # 78 - 7f
- 0,0,0,0,0,0,0,0, # 80 - 87
- 0,0,0,0,0,0,0,0, # 88 - 8f
- 0,0,0,0,0,0,0,0, # 90 - 97
- 0,0,0,0,0,0,0,0, # 98 - 9f
- 0,2,2,2,2,2,2,2, # a0 - a7
- 2,2,2,2,2,3,3,3, # a8 - af
- 2,2,2,2,2,2,2,2, # b0 - b7
- 2,2,2,2,2,2,2,2, # b8 - bf
- 2,2,2,2,2,2,2,2, # c0 - c7
- 2,3,2,2,2,2,2,2, # c8 - cf
- 2,2,2,2,2,2,2,2, # d0 - d7
- 2,2,2,2,2,2,2,2, # d8 - df
- 2,2,2,2,2,2,2,2, # e0 - e7
- 2,2,2,2,2,2,2,2, # e8 - ef
- 2,2,2,2,2,2,2,2, # f0 - f7
- 2,2,2,2,2,2,2,0 # f8 - ff
+ 1,1,1,1,1,1,1,2, # 78 - 7f
+ 6,6,6,6,8,8,8,8, # 80 - 87
+ 8,8,8,8,8,8,8,8, # 88 - 8f
+ 8,7,7,7,7,7,7,7, # 90 - 97
+ 7,7,7,7,7,7,7,7, # 98 - 9f
+ 7,7,7,7,7,7,7,7, # a0 - a7
+ 7,7,7,7,7,7,7,7, # a8 - af
+ 7,7,7,7,7,7,7,7, # b0 - b7
+ 7,7,7,7,7,7,7,7, # b8 - bf
+ 7,7,7,7,7,7,7,7, # c0 - c7
+ 7,7,7,7,7,7,7,7, # c8 - cf
+ 7,7,7,7,5,5,5,5, # d0 - d7
+ 5,9,9,9,9,9,9,5, # d8 - df
+ 9,9,9,9,9,9,9,9, # e0 - e7
+ 9,9,9,9,9,9,9,9, # e8 - ef
+ 9,9,9,9,9,9,9,9, # f0 - f7
+ 9,9,5,5,5,5,5,0 # f8 - ff
)
-EUCKR_ST = (
- MachineState.ERROR,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07
- MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START #08-0f
+JOHAB_ST = (
+# cls = 0 1 2 3 4 5 6 7 8 9
+ MachineState.ERROR ,MachineState.START ,MachineState.START ,MachineState.START ,MachineState.START ,MachineState.ERROR ,MachineState.ERROR ,3 ,3 ,4 , # MachineState.START
+ MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME, # MachineState.ITS_ME
+ MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR ,MachineState.ERROR , # MachineState.ERROR
+ MachineState.ERROR ,MachineState.START ,MachineState.START ,MachineState.ERROR ,MachineState.ERROR ,MachineState.START ,MachineState.START ,MachineState.START ,MachineState.START ,MachineState.START , # 3
+ MachineState.ERROR ,MachineState.START ,MachineState.ERROR ,MachineState.START ,MachineState.ERROR ,MachineState.START ,MachineState.ERROR ,MachineState.START ,MachineState.ERROR ,MachineState.START , # 4
)
+# fmt: on
-EUCKR_CHAR_LEN_TABLE = (0, 1, 2, 0)
+JOHAB_CHAR_LEN_TABLE = (0, 1, 1, 1, 1, 0, 0, 2, 2, 2)
-EUCKR_SM_MODEL = {'class_table': EUCKR_CLS,
- 'class_factor': 4,
- 'state_table': EUCKR_ST,
- 'char_len_table': EUCKR_CHAR_LEN_TABLE,
- 'name': 'EUC-KR'}
+JOHAB_SM_MODEL: CodingStateMachineDict = {
+ "class_table": JOHAB_CLS,
+ "class_factor": 10,
+ "state_table": JOHAB_ST,
+ "char_len_table": JOHAB_CHAR_LEN_TABLE,
+ "name": "Johab",
+}
# EUC-TW
-
+# fmt: off
EUCTW_CLS = (
- 2,2,2,2,2,2,2,2, # 00 - 07
- 2,2,2,2,2,2,0,0, # 08 - 0f
- 2,2,2,2,2,2,2,2, # 10 - 17
- 2,2,2,0,2,2,2,2, # 18 - 1f
- 2,2,2,2,2,2,2,2, # 20 - 27
- 2,2,2,2,2,2,2,2, # 28 - 2f
- 2,2,2,2,2,2,2,2, # 30 - 37
- 2,2,2,2,2,2,2,2, # 38 - 3f
- 2,2,2,2,2,2,2,2, # 40 - 47
- 2,2,2,2,2,2,2,2, # 48 - 4f
- 2,2,2,2,2,2,2,2, # 50 - 57
- 2,2,2,2,2,2,2,2, # 58 - 5f
- 2,2,2,2,2,2,2,2, # 60 - 67
- 2,2,2,2,2,2,2,2, # 68 - 6f
- 2,2,2,2,2,2,2,2, # 70 - 77
- 2,2,2,2,2,2,2,2, # 78 - 7f
- 0,0,0,0,0,0,0,0, # 80 - 87
- 0,0,0,0,0,0,6,0, # 88 - 8f
- 0,0,0,0,0,0,0,0, # 90 - 97
- 0,0,0,0,0,0,0,0, # 98 - 9f
- 0,3,4,4,4,4,4,4, # a0 - a7
- 5,5,1,1,1,1,1,1, # a8 - af
- 1,1,1,1,1,1,1,1, # b0 - b7
- 1,1,1,1,1,1,1,1, # b8 - bf
- 1,1,3,1,3,3,3,3, # c0 - c7
- 3,3,3,3,3,3,3,3, # c8 - cf
- 3,3,3,3,3,3,3,3, # d0 - d7
- 3,3,3,3,3,3,3,3, # d8 - df
- 3,3,3,3,3,3,3,3, # e0 - e7
- 3,3,3,3,3,3,3,3, # e8 - ef
- 3,3,3,3,3,3,3,3, # f0 - f7
- 3,3,3,3,3,3,3,0 # f8 - ff
+ 2, 2, 2, 2, 2, 2, 2, 2, # 00 - 07
+ 2, 2, 2, 2, 2, 2, 0, 0, # 08 - 0f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 10 - 17
+ 2, 2, 2, 0, 2, 2, 2, 2, # 18 - 1f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 20 - 27
+ 2, 2, 2, 2, 2, 2, 2, 2, # 28 - 2f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 30 - 37
+ 2, 2, 2, 2, 2, 2, 2, 2, # 38 - 3f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 40 - 47
+ 2, 2, 2, 2, 2, 2, 2, 2, # 48 - 4f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 50 - 57
+ 2, 2, 2, 2, 2, 2, 2, 2, # 58 - 5f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 60 - 67
+ 2, 2, 2, 2, 2, 2, 2, 2, # 68 - 6f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 70 - 77
+ 2, 2, 2, 2, 2, 2, 2, 2, # 78 - 7f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 80 - 87
+ 0, 0, 0, 0, 0, 0, 6, 0, # 88 - 8f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 90 - 97
+ 0, 0, 0, 0, 0, 0, 0, 0, # 98 - 9f
+ 0, 3, 4, 4, 4, 4, 4, 4, # a0 - a7
+ 5, 5, 1, 1, 1, 1, 1, 1, # a8 - af
+ 1, 1, 1, 1, 1, 1, 1, 1, # b0 - b7
+ 1, 1, 1, 1, 1, 1, 1, 1, # b8 - bf
+ 1, 1, 3, 1, 3, 3, 3, 3, # c0 - c7
+ 3, 3, 3, 3, 3, 3, 3, 3, # c8 - cf
+ 3, 3, 3, 3, 3, 3, 3, 3, # d0 - d7
+ 3, 3, 3, 3, 3, 3, 3, 3, # d8 - df
+ 3, 3, 3, 3, 3, 3, 3, 3, # e0 - e7
+ 3, 3, 3, 3, 3, 3, 3, 3, # e8 - ef
+ 3, 3, 3, 3, 3, 3, 3, 3, # f0 - f7
+ 3, 3, 3, 3, 3, 3, 3, 0 # f8 - ff
)
EUCTW_ST = (
@@ -266,50 +337,53 @@ EUCTW_ST = (
5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.START,MachineState.START,#20-27
MachineState.START,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START #28-2f
)
+# fmt: on
EUCTW_CHAR_LEN_TABLE = (0, 0, 1, 2, 2, 2, 3)
-EUCTW_SM_MODEL = {'class_table': EUCTW_CLS,
- 'class_factor': 7,
- 'state_table': EUCTW_ST,
- 'char_len_table': EUCTW_CHAR_LEN_TABLE,
- 'name': 'x-euc-tw'}
+EUCTW_SM_MODEL: CodingStateMachineDict = {
+ "class_table": EUCTW_CLS,
+ "class_factor": 7,
+ "state_table": EUCTW_ST,
+ "char_len_table": EUCTW_CHAR_LEN_TABLE,
+ "name": "x-euc-tw",
+}
# GB2312
-
+# fmt: off
GB2312_CLS = (
- 1,1,1,1,1,1,1,1, # 00 - 07
- 1,1,1,1,1,1,0,0, # 08 - 0f
- 1,1,1,1,1,1,1,1, # 10 - 17
- 1,1,1,0,1,1,1,1, # 18 - 1f
- 1,1,1,1,1,1,1,1, # 20 - 27
- 1,1,1,1,1,1,1,1, # 28 - 2f
- 3,3,3,3,3,3,3,3, # 30 - 37
- 3,3,1,1,1,1,1,1, # 38 - 3f
- 2,2,2,2,2,2,2,2, # 40 - 47
- 2,2,2,2,2,2,2,2, # 48 - 4f
- 2,2,2,2,2,2,2,2, # 50 - 57
- 2,2,2,2,2,2,2,2, # 58 - 5f
- 2,2,2,2,2,2,2,2, # 60 - 67
- 2,2,2,2,2,2,2,2, # 68 - 6f
- 2,2,2,2,2,2,2,2, # 70 - 77
- 2,2,2,2,2,2,2,4, # 78 - 7f
- 5,6,6,6,6,6,6,6, # 80 - 87
- 6,6,6,6,6,6,6,6, # 88 - 8f
- 6,6,6,6,6,6,6,6, # 90 - 97
- 6,6,6,6,6,6,6,6, # 98 - 9f
- 6,6,6,6,6,6,6,6, # a0 - a7
- 6,6,6,6,6,6,6,6, # a8 - af
- 6,6,6,6,6,6,6,6, # b0 - b7
- 6,6,6,6,6,6,6,6, # b8 - bf
- 6,6,6,6,6,6,6,6, # c0 - c7
- 6,6,6,6,6,6,6,6, # c8 - cf
- 6,6,6,6,6,6,6,6, # d0 - d7
- 6,6,6,6,6,6,6,6, # d8 - df
- 6,6,6,6,6,6,6,6, # e0 - e7
- 6,6,6,6,6,6,6,6, # e8 - ef
- 6,6,6,6,6,6,6,6, # f0 - f7
- 6,6,6,6,6,6,6,0 # f8 - ff
+ 1, 1, 1, 1, 1, 1, 1, 1, # 00 - 07
+ 1, 1, 1, 1, 1, 1, 0, 0, # 08 - 0f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 10 - 17
+ 1, 1, 1, 0, 1, 1, 1, 1, # 18 - 1f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 20 - 27
+ 1, 1, 1, 1, 1, 1, 1, 1, # 28 - 2f
+ 3, 3, 3, 3, 3, 3, 3, 3, # 30 - 37
+ 3, 3, 1, 1, 1, 1, 1, 1, # 38 - 3f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 40 - 47
+ 2, 2, 2, 2, 2, 2, 2, 2, # 48 - 4f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 50 - 57
+ 2, 2, 2, 2, 2, 2, 2, 2, # 58 - 5f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 60 - 67
+ 2, 2, 2, 2, 2, 2, 2, 2, # 68 - 6f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 70 - 77
+ 2, 2, 2, 2, 2, 2, 2, 4, # 78 - 7f
+ 5, 6, 6, 6, 6, 6, 6, 6, # 80 - 87
+ 6, 6, 6, 6, 6, 6, 6, 6, # 88 - 8f
+ 6, 6, 6, 6, 6, 6, 6, 6, # 90 - 97
+ 6, 6, 6, 6, 6, 6, 6, 6, # 98 - 9f
+ 6, 6, 6, 6, 6, 6, 6, 6, # a0 - a7
+ 6, 6, 6, 6, 6, 6, 6, 6, # a8 - af
+ 6, 6, 6, 6, 6, 6, 6, 6, # b0 - b7
+ 6, 6, 6, 6, 6, 6, 6, 6, # b8 - bf
+ 6, 6, 6, 6, 6, 6, 6, 6, # c0 - c7
+ 6, 6, 6, 6, 6, 6, 6, 6, # c8 - cf
+ 6, 6, 6, 6, 6, 6, 6, 6, # d0 - d7
+ 6, 6, 6, 6, 6, 6, 6, 6, # d8 - df
+ 6, 6, 6, 6, 6, 6, 6, 6, # e0 - e7
+ 6, 6, 6, 6, 6, 6, 6, 6, # e8 - ef
+ 6, 6, 6, 6, 6, 6, 6, 6, # f0 - f7
+ 6, 6, 6, 6, 6, 6, 6, 0 # f8 - ff
)
GB2312_ST = (
@@ -320,6 +394,7 @@ GB2312_ST = (
MachineState.ERROR,MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,#20-27
MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START #28-2f
)
+# fmt: on
# To be accurate, the length of class 6 can be either 2 or 4.
# But it is not necessary to discriminate between the two since
@@ -328,100 +403,105 @@ GB2312_ST = (
# 2 here.
GB2312_CHAR_LEN_TABLE = (0, 1, 1, 1, 1, 1, 2)
-GB2312_SM_MODEL = {'class_table': GB2312_CLS,
- 'class_factor': 7,
- 'state_table': GB2312_ST,
- 'char_len_table': GB2312_CHAR_LEN_TABLE,
- 'name': 'GB2312'}
+GB2312_SM_MODEL: CodingStateMachineDict = {
+ "class_table": GB2312_CLS,
+ "class_factor": 7,
+ "state_table": GB2312_ST,
+ "char_len_table": GB2312_CHAR_LEN_TABLE,
+ "name": "GB2312",
+}
# Shift_JIS
-
+# fmt: off
SJIS_CLS = (
- 1,1,1,1,1,1,1,1, # 00 - 07
- 1,1,1,1,1,1,0,0, # 08 - 0f
- 1,1,1,1,1,1,1,1, # 10 - 17
- 1,1,1,0,1,1,1,1, # 18 - 1f
- 1,1,1,1,1,1,1,1, # 20 - 27
- 1,1,1,1,1,1,1,1, # 28 - 2f
- 1,1,1,1,1,1,1,1, # 30 - 37
- 1,1,1,1,1,1,1,1, # 38 - 3f
- 2,2,2,2,2,2,2,2, # 40 - 47
- 2,2,2,2,2,2,2,2, # 48 - 4f
- 2,2,2,2,2,2,2,2, # 50 - 57
- 2,2,2,2,2,2,2,2, # 58 - 5f
- 2,2,2,2,2,2,2,2, # 60 - 67
- 2,2,2,2,2,2,2,2, # 68 - 6f
- 2,2,2,2,2,2,2,2, # 70 - 77
- 2,2,2,2,2,2,2,1, # 78 - 7f
- 3,3,3,3,3,2,2,3, # 80 - 87
- 3,3,3,3,3,3,3,3, # 88 - 8f
- 3,3,3,3,3,3,3,3, # 90 - 97
- 3,3,3,3,3,3,3,3, # 98 - 9f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 00 - 07
+ 1, 1, 1, 1, 1, 1, 0, 0, # 08 - 0f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 10 - 17
+ 1, 1, 1, 0, 1, 1, 1, 1, # 18 - 1f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 20 - 27
+ 1, 1, 1, 1, 1, 1, 1, 1, # 28 - 2f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 30 - 37
+ 1, 1, 1, 1, 1, 1, 1, 1, # 38 - 3f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 40 - 47
+ 2, 2, 2, 2, 2, 2, 2, 2, # 48 - 4f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 50 - 57
+ 2, 2, 2, 2, 2, 2, 2, 2, # 58 - 5f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 60 - 67
+ 2, 2, 2, 2, 2, 2, 2, 2, # 68 - 6f
+ 2, 2, 2, 2, 2, 2, 2, 2, # 70 - 77
+ 2, 2, 2, 2, 2, 2, 2, 1, # 78 - 7f
+ 3, 3, 3, 3, 3, 2, 2, 3, # 80 - 87
+ 3, 3, 3, 3, 3, 3, 3, 3, # 88 - 8f
+ 3, 3, 3, 3, 3, 3, 3, 3, # 90 - 97
+ 3, 3, 3, 3, 3, 3, 3, 3, # 98 - 9f
#0xa0 is illegal in sjis encoding, but some pages does
#contain such byte. We need to be more error forgiven.
- 2,2,2,2,2,2,2,2, # a0 - a7
- 2,2,2,2,2,2,2,2, # a8 - af
- 2,2,2,2,2,2,2,2, # b0 - b7
- 2,2,2,2,2,2,2,2, # b8 - bf
- 2,2,2,2,2,2,2,2, # c0 - c7
- 2,2,2,2,2,2,2,2, # c8 - cf
- 2,2,2,2,2,2,2,2, # d0 - d7
- 2,2,2,2,2,2,2,2, # d8 - df
- 3,3,3,3,3,3,3,3, # e0 - e7
- 3,3,3,3,3,4,4,4, # e8 - ef
- 3,3,3,3,3,3,3,3, # f0 - f7
- 3,3,3,3,3,0,0,0) # f8 - ff
-
+ 2, 2, 2, 2, 2, 2, 2, 2, # a0 - a7
+ 2, 2, 2, 2, 2, 2, 2, 2, # a8 - af
+ 2, 2, 2, 2, 2, 2, 2, 2, # b0 - b7
+ 2, 2, 2, 2, 2, 2, 2, 2, # b8 - bf
+ 2, 2, 2, 2, 2, 2, 2, 2, # c0 - c7
+ 2, 2, 2, 2, 2, 2, 2, 2, # c8 - cf
+ 2, 2, 2, 2, 2, 2, 2, 2, # d0 - d7
+ 2, 2, 2, 2, 2, 2, 2, 2, # d8 - df
+ 3, 3, 3, 3, 3, 3, 3, 3, # e0 - e7
+ 3, 3, 3, 3, 3, 4, 4, 4, # e8 - ef
+ 3, 3, 3, 3, 3, 3, 3, 3, # f0 - f7
+ 3, 3, 3, 3, 3, 0, 0, 0, # f8 - ff
+)
SJIS_ST = (
MachineState.ERROR,MachineState.START,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07
MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f
MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START #10-17
)
+# fmt: on
SJIS_CHAR_LEN_TABLE = (0, 1, 1, 2, 0, 0)
-SJIS_SM_MODEL = {'class_table': SJIS_CLS,
- 'class_factor': 6,
- 'state_table': SJIS_ST,
- 'char_len_table': SJIS_CHAR_LEN_TABLE,
- 'name': 'Shift_JIS'}
+SJIS_SM_MODEL: CodingStateMachineDict = {
+ "class_table": SJIS_CLS,
+ "class_factor": 6,
+ "state_table": SJIS_ST,
+ "char_len_table": SJIS_CHAR_LEN_TABLE,
+ "name": "Shift_JIS",
+}
# UCS2-BE
-
+# fmt: off
UCS2BE_CLS = (
- 0,0,0,0,0,0,0,0, # 00 - 07
- 0,0,1,0,0,2,0,0, # 08 - 0f
- 0,0,0,0,0,0,0,0, # 10 - 17
- 0,0,0,3,0,0,0,0, # 18 - 1f
- 0,0,0,0,0,0,0,0, # 20 - 27
- 0,3,3,3,3,3,0,0, # 28 - 2f
- 0,0,0,0,0,0,0,0, # 30 - 37
- 0,0,0,0,0,0,0,0, # 38 - 3f
- 0,0,0,0,0,0,0,0, # 40 - 47
- 0,0,0,0,0,0,0,0, # 48 - 4f
- 0,0,0,0,0,0,0,0, # 50 - 57
- 0,0,0,0,0,0,0,0, # 58 - 5f
- 0,0,0,0,0,0,0,0, # 60 - 67
- 0,0,0,0,0,0,0,0, # 68 - 6f
- 0,0,0,0,0,0,0,0, # 70 - 77
- 0,0,0,0,0,0,0,0, # 78 - 7f
- 0,0,0,0,0,0,0,0, # 80 - 87
- 0,0,0,0,0,0,0,0, # 88 - 8f
- 0,0,0,0,0,0,0,0, # 90 - 97
- 0,0,0,0,0,0,0,0, # 98 - 9f
- 0,0,0,0,0,0,0,0, # a0 - a7
- 0,0,0,0,0,0,0,0, # a8 - af
- 0,0,0,0,0,0,0,0, # b0 - b7
- 0,0,0,0,0,0,0,0, # b8 - bf
- 0,0,0,0,0,0,0,0, # c0 - c7
- 0,0,0,0,0,0,0,0, # c8 - cf
- 0,0,0,0,0,0,0,0, # d0 - d7
- 0,0,0,0,0,0,0,0, # d8 - df
- 0,0,0,0,0,0,0,0, # e0 - e7
- 0,0,0,0,0,0,0,0, # e8 - ef
- 0,0,0,0,0,0,0,0, # f0 - f7
- 0,0,0,0,0,0,4,5 # f8 - ff
+ 0, 0, 0, 0, 0, 0, 0, 0, # 00 - 07
+ 0, 0, 1, 0, 0, 2, 0, 0, # 08 - 0f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 10 - 17
+ 0, 0, 0, 3, 0, 0, 0, 0, # 18 - 1f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 20 - 27
+ 0, 3, 3, 3, 3, 3, 0, 0, # 28 - 2f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 30 - 37
+ 0, 0, 0, 0, 0, 0, 0, 0, # 38 - 3f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 40 - 47
+ 0, 0, 0, 0, 0, 0, 0, 0, # 48 - 4f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 50 - 57
+ 0, 0, 0, 0, 0, 0, 0, 0, # 58 - 5f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 60 - 67
+ 0, 0, 0, 0, 0, 0, 0, 0, # 68 - 6f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 70 - 77
+ 0, 0, 0, 0, 0, 0, 0, 0, # 78 - 7f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 80 - 87
+ 0, 0, 0, 0, 0, 0, 0, 0, # 88 - 8f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 90 - 97
+ 0, 0, 0, 0, 0, 0, 0, 0, # 98 - 9f
+ 0, 0, 0, 0, 0, 0, 0, 0, # a0 - a7
+ 0, 0, 0, 0, 0, 0, 0, 0, # a8 - af
+ 0, 0, 0, 0, 0, 0, 0, 0, # b0 - b7
+ 0, 0, 0, 0, 0, 0, 0, 0, # b8 - bf
+ 0, 0, 0, 0, 0, 0, 0, 0, # c0 - c7
+ 0, 0, 0, 0, 0, 0, 0, 0, # c8 - cf
+ 0, 0, 0, 0, 0, 0, 0, 0, # d0 - d7
+ 0, 0, 0, 0, 0, 0, 0, 0, # d8 - df
+ 0, 0, 0, 0, 0, 0, 0, 0, # e0 - e7
+ 0, 0, 0, 0, 0, 0, 0, 0, # e8 - ef
+ 0, 0, 0, 0, 0, 0, 0, 0, # f0 - f7
+ 0, 0, 0, 0, 0, 0, 4, 5 # f8 - ff
)
UCS2BE_ST = (
@@ -433,50 +513,53 @@ UCS2BE_ST = (
5, 8, 6, 6,MachineState.ERROR, 6, 6, 6,#28-2f
6, 6, 6, 6,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START #30-37
)
+# fmt: on
UCS2BE_CHAR_LEN_TABLE = (2, 2, 2, 0, 2, 2)
-UCS2BE_SM_MODEL = {'class_table': UCS2BE_CLS,
- 'class_factor': 6,
- 'state_table': UCS2BE_ST,
- 'char_len_table': UCS2BE_CHAR_LEN_TABLE,
- 'name': 'UTF-16BE'}
+UCS2BE_SM_MODEL: CodingStateMachineDict = {
+ "class_table": UCS2BE_CLS,
+ "class_factor": 6,
+ "state_table": UCS2BE_ST,
+ "char_len_table": UCS2BE_CHAR_LEN_TABLE,
+ "name": "UTF-16BE",
+}
# UCS2-LE
-
+# fmt: off
UCS2LE_CLS = (
- 0,0,0,0,0,0,0,0, # 00 - 07
- 0,0,1,0,0,2,0,0, # 08 - 0f
- 0,0,0,0,0,0,0,0, # 10 - 17
- 0,0,0,3,0,0,0,0, # 18 - 1f
- 0,0,0,0,0,0,0,0, # 20 - 27
- 0,3,3,3,3,3,0,0, # 28 - 2f
- 0,0,0,0,0,0,0,0, # 30 - 37
- 0,0,0,0,0,0,0,0, # 38 - 3f
- 0,0,0,0,0,0,0,0, # 40 - 47
- 0,0,0,0,0,0,0,0, # 48 - 4f
- 0,0,0,0,0,0,0,0, # 50 - 57
- 0,0,0,0,0,0,0,0, # 58 - 5f
- 0,0,0,0,0,0,0,0, # 60 - 67
- 0,0,0,0,0,0,0,0, # 68 - 6f
- 0,0,0,0,0,0,0,0, # 70 - 77
- 0,0,0,0,0,0,0,0, # 78 - 7f
- 0,0,0,0,0,0,0,0, # 80 - 87
- 0,0,0,0,0,0,0,0, # 88 - 8f
- 0,0,0,0,0,0,0,0, # 90 - 97
- 0,0,0,0,0,0,0,0, # 98 - 9f
- 0,0,0,0,0,0,0,0, # a0 - a7
- 0,0,0,0,0,0,0,0, # a8 - af
- 0,0,0,0,0,0,0,0, # b0 - b7
- 0,0,0,0,0,0,0,0, # b8 - bf
- 0,0,0,0,0,0,0,0, # c0 - c7
- 0,0,0,0,0,0,0,0, # c8 - cf
- 0,0,0,0,0,0,0,0, # d0 - d7
- 0,0,0,0,0,0,0,0, # d8 - df
- 0,0,0,0,0,0,0,0, # e0 - e7
- 0,0,0,0,0,0,0,0, # e8 - ef
- 0,0,0,0,0,0,0,0, # f0 - f7
- 0,0,0,0,0,0,4,5 # f8 - ff
+ 0, 0, 0, 0, 0, 0, 0, 0, # 00 - 07
+ 0, 0, 1, 0, 0, 2, 0, 0, # 08 - 0f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 10 - 17
+ 0, 0, 0, 3, 0, 0, 0, 0, # 18 - 1f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 20 - 27
+ 0, 3, 3, 3, 3, 3, 0, 0, # 28 - 2f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 30 - 37
+ 0, 0, 0, 0, 0, 0, 0, 0, # 38 - 3f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 40 - 47
+ 0, 0, 0, 0, 0, 0, 0, 0, # 48 - 4f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 50 - 57
+ 0, 0, 0, 0, 0, 0, 0, 0, # 58 - 5f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 60 - 67
+ 0, 0, 0, 0, 0, 0, 0, 0, # 68 - 6f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 70 - 77
+ 0, 0, 0, 0, 0, 0, 0, 0, # 78 - 7f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 80 - 87
+ 0, 0, 0, 0, 0, 0, 0, 0, # 88 - 8f
+ 0, 0, 0, 0, 0, 0, 0, 0, # 90 - 97
+ 0, 0, 0, 0, 0, 0, 0, 0, # 98 - 9f
+ 0, 0, 0, 0, 0, 0, 0, 0, # a0 - a7
+ 0, 0, 0, 0, 0, 0, 0, 0, # a8 - af
+ 0, 0, 0, 0, 0, 0, 0, 0, # b0 - b7
+ 0, 0, 0, 0, 0, 0, 0, 0, # b8 - bf
+ 0, 0, 0, 0, 0, 0, 0, 0, # c0 - c7
+ 0, 0, 0, 0, 0, 0, 0, 0, # c8 - cf
+ 0, 0, 0, 0, 0, 0, 0, 0, # d0 - d7
+ 0, 0, 0, 0, 0, 0, 0, 0, # d8 - df
+ 0, 0, 0, 0, 0, 0, 0, 0, # e0 - e7
+ 0, 0, 0, 0, 0, 0, 0, 0, # e8 - ef
+ 0, 0, 0, 0, 0, 0, 0, 0, # f0 - f7
+ 0, 0, 0, 0, 0, 0, 4, 5 # f8 - ff
)
UCS2LE_ST = (
@@ -488,50 +571,53 @@ UCS2LE_ST = (
5, 5, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5, 5,#28-2f
5, 5, 5,MachineState.ERROR, 5,MachineState.ERROR,MachineState.START,MachineState.START #30-37
)
+# fmt: on
UCS2LE_CHAR_LEN_TABLE = (2, 2, 2, 2, 2, 2)
-UCS2LE_SM_MODEL = {'class_table': UCS2LE_CLS,
- 'class_factor': 6,
- 'state_table': UCS2LE_ST,
- 'char_len_table': UCS2LE_CHAR_LEN_TABLE,
- 'name': 'UTF-16LE'}
+UCS2LE_SM_MODEL: CodingStateMachineDict = {
+ "class_table": UCS2LE_CLS,
+ "class_factor": 6,
+ "state_table": UCS2LE_ST,
+ "char_len_table": UCS2LE_CHAR_LEN_TABLE,
+ "name": "UTF-16LE",
+}
# UTF-8
-
+# fmt: off
UTF8_CLS = (
- 1,1,1,1,1,1,1,1, # 00 - 07 #allow 0x00 as a legal value
- 1,1,1,1,1,1,0,0, # 08 - 0f
- 1,1,1,1,1,1,1,1, # 10 - 17
- 1,1,1,0,1,1,1,1, # 18 - 1f
- 1,1,1,1,1,1,1,1, # 20 - 27
- 1,1,1,1,1,1,1,1, # 28 - 2f
- 1,1,1,1,1,1,1,1, # 30 - 37
- 1,1,1,1,1,1,1,1, # 38 - 3f
- 1,1,1,1,1,1,1,1, # 40 - 47
- 1,1,1,1,1,1,1,1, # 48 - 4f
- 1,1,1,1,1,1,1,1, # 50 - 57
- 1,1,1,1,1,1,1,1, # 58 - 5f
- 1,1,1,1,1,1,1,1, # 60 - 67
- 1,1,1,1,1,1,1,1, # 68 - 6f
- 1,1,1,1,1,1,1,1, # 70 - 77
- 1,1,1,1,1,1,1,1, # 78 - 7f
- 2,2,2,2,3,3,3,3, # 80 - 87
- 4,4,4,4,4,4,4,4, # 88 - 8f
- 4,4,4,4,4,4,4,4, # 90 - 97
- 4,4,4,4,4,4,4,4, # 98 - 9f
- 5,5,5,5,5,5,5,5, # a0 - a7
- 5,5,5,5,5,5,5,5, # a8 - af
- 5,5,5,5,5,5,5,5, # b0 - b7
- 5,5,5,5,5,5,5,5, # b8 - bf
- 0,0,6,6,6,6,6,6, # c0 - c7
- 6,6,6,6,6,6,6,6, # c8 - cf
- 6,6,6,6,6,6,6,6, # d0 - d7
- 6,6,6,6,6,6,6,6, # d8 - df
- 7,8,8,8,8,8,8,8, # e0 - e7
- 8,8,8,8,8,9,8,8, # e8 - ef
- 10,11,11,11,11,11,11,11, # f0 - f7
- 12,13,13,13,14,15,0,0 # f8 - ff
+ 1, 1, 1, 1, 1, 1, 1, 1, # 00 - 07 #allow 0x00 as a legal value
+ 1, 1, 1, 1, 1, 1, 0, 0, # 08 - 0f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 10 - 17
+ 1, 1, 1, 0, 1, 1, 1, 1, # 18 - 1f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 20 - 27
+ 1, 1, 1, 1, 1, 1, 1, 1, # 28 - 2f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 30 - 37
+ 1, 1, 1, 1, 1, 1, 1, 1, # 38 - 3f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 40 - 47
+ 1, 1, 1, 1, 1, 1, 1, 1, # 48 - 4f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 50 - 57
+ 1, 1, 1, 1, 1, 1, 1, 1, # 58 - 5f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 60 - 67
+ 1, 1, 1, 1, 1, 1, 1, 1, # 68 - 6f
+ 1, 1, 1, 1, 1, 1, 1, 1, # 70 - 77
+ 1, 1, 1, 1, 1, 1, 1, 1, # 78 - 7f
+ 2, 2, 2, 2, 3, 3, 3, 3, # 80 - 87
+ 4, 4, 4, 4, 4, 4, 4, 4, # 88 - 8f
+ 4, 4, 4, 4, 4, 4, 4, 4, # 90 - 97
+ 4, 4, 4, 4, 4, 4, 4, 4, # 98 - 9f
+ 5, 5, 5, 5, 5, 5, 5, 5, # a0 - a7
+ 5, 5, 5, 5, 5, 5, 5, 5, # a8 - af
+ 5, 5, 5, 5, 5, 5, 5, 5, # b0 - b7
+ 5, 5, 5, 5, 5, 5, 5, 5, # b8 - bf
+ 0, 0, 6, 6, 6, 6, 6, 6, # c0 - c7
+ 6, 6, 6, 6, 6, 6, 6, 6, # c8 - cf
+ 6, 6, 6, 6, 6, 6, 6, 6, # d0 - d7
+ 6, 6, 6, 6, 6, 6, 6, 6, # d8 - df
+ 7, 8, 8, 8, 8, 8, 8, 8, # e0 - e7
+ 8, 8, 8, 8, 8, 9, 8, 8, # e8 - ef
+ 10, 11, 11, 11, 11, 11, 11, 11, # f0 - f7
+ 12, 13, 13, 13, 14, 15, 0, 0 # f8 - ff
)
UTF8_ST = (
@@ -562,11 +648,14 @@ UTF8_ST = (
MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,#c0-c7
MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR #c8-cf
)
+# fmt: on
UTF8_CHAR_LEN_TABLE = (0, 1, 0, 0, 0, 0, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6)
-UTF8_SM_MODEL = {'class_table': UTF8_CLS,
- 'class_factor': 16,
- 'state_table': UTF8_ST,
- 'char_len_table': UTF8_CHAR_LEN_TABLE,
- 'name': 'UTF-8'}
+UTF8_SM_MODEL: CodingStateMachineDict = {
+ "class_table": UTF8_CLS,
+ "class_factor": 16,
+ "state_table": UTF8_ST,
+ "char_len_table": UTF8_CHAR_LEN_TABLE,
+ "name": "UTF-8",
+}
diff --git a/src/pip/_vendor/chardet/metadata/languages.py b/src/pip/_vendor/chardet/metadata/languages.py
index 3237d5abf..eb40c5f0c 100644
--- a/src/pip/_vendor/chardet/metadata/languages.py
+++ b/src/pip/_vendor/chardet/metadata/languages.py
@@ -1,19 +1,17 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
"""
Metadata about languages used by our model training code for our
SingleByteCharSetProbers. Could be used for other things in the future.
This code is based on the language metadata from the uchardet project.
"""
-from __future__ import absolute_import, print_function
from string import ascii_letters
+from typing import List, Optional
+# TODO: Add Ukrainian (KOI8-U)
-# TODO: Add Ukranian (KOI8-U)
-class Language(object):
+class Language:
"""Metadata about a language useful for training models
:ivar name: The human name for the language, in English.
@@ -33,9 +31,17 @@ class Language(object):
Wikipedia for training data.
:type wiki_start_pages: list of str
"""
- def __init__(self, name=None, iso_code=None, use_ascii=True, charsets=None,
- alphabet=None, wiki_start_pages=None):
- super(Language, self).__init__()
+
+ def __init__(
+ self,
+ name: Optional[str] = None,
+ iso_code: Optional[str] = None,
+ use_ascii: bool = True,
+ charsets: Optional[List[str]] = None,
+ alphabet: Optional[str] = None,
+ wiki_start_pages: Optional[List[str]] = None,
+ ) -> None:
+ super().__init__()
self.name = name
self.iso_code = iso_code
self.use_ascii = use_ascii
@@ -46,265 +52,301 @@ class Language(object):
else:
alphabet = ascii_letters
elif not alphabet:
- raise ValueError('Must supply alphabet if use_ascii is False')
- self.alphabet = ''.join(sorted(set(alphabet))) if alphabet else None
+ raise ValueError("Must supply alphabet if use_ascii is False")
+ self.alphabet = "".join(sorted(set(alphabet))) if alphabet else None
self.wiki_start_pages = wiki_start_pages
- def __repr__(self):
- return '{}({})'.format(self.__class__.__name__,
- ', '.join('{}={!r}'.format(k, v)
- for k, v in self.__dict__.items()
- if not k.startswith('_')))
+ def __repr__(self) -> str:
+ param_str = ", ".join(
+ f"{k}={v!r}" for k, v in self.__dict__.items() if not k.startswith("_")
+ )
+ return f"{self.__class__.__name__}({param_str})"
-LANGUAGES = {'Arabic': Language(name='Arabic',
- iso_code='ar',
- use_ascii=False,
- # We only support encodings that use isolated
- # forms, because the current recommendation is
- # that the rendering system handles presentation
- # forms. This means we purposefully skip IBM864.
- charsets=['ISO-8859-6', 'WINDOWS-1256',
- 'CP720', 'CP864'],
- alphabet=u'ءآأؤإئابةتثجحخدذرزسشصضطظعغػؼؽؾؿـفقكلمنهوىيًٌٍَُِّ',
- wiki_start_pages=[u'الصفحة_الرئيسية']),
- 'Belarusian': Language(name='Belarusian',
- iso_code='be',
- use_ascii=False,
- charsets=['ISO-8859-5', 'WINDOWS-1251',
- 'IBM866', 'MacCyrillic'],
- alphabet=(u'АБВГДЕЁЖЗІЙКЛМНОПРСТУЎФХЦЧШЫЬЭЮЯ'
- u'абвгдеёжзійклмнопрстуўфхцчшыьэюяʼ'),
- wiki_start_pages=[u'Галоўная_старонка']),
- 'Bulgarian': Language(name='Bulgarian',
- iso_code='bg',
- use_ascii=False,
- charsets=['ISO-8859-5', 'WINDOWS-1251',
- 'IBM855'],
- alphabet=(u'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯ'
- u'абвгдежзийклмнопрстуфхцчшщъьюя'),
- wiki_start_pages=[u'Начална_страница']),
- 'Czech': Language(name='Czech',
- iso_code='cz',
- use_ascii=True,
- charsets=['ISO-8859-2', 'WINDOWS-1250'],
- alphabet=u'áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ',
- wiki_start_pages=[u'Hlavní_strana']),
- 'Danish': Language(name='Danish',
- iso_code='da',
- use_ascii=True,
- charsets=['ISO-8859-1', 'ISO-8859-15',
- 'WINDOWS-1252'],
- alphabet=u'æøåÆØÅ',
- wiki_start_pages=[u'Forside']),
- 'German': Language(name='German',
- iso_code='de',
- use_ascii=True,
- charsets=['ISO-8859-1', 'WINDOWS-1252'],
- alphabet=u'äöüßÄÖÜ',
- wiki_start_pages=[u'Wikipedia:Hauptseite']),
- 'Greek': Language(name='Greek',
- iso_code='el',
- use_ascii=False,
- charsets=['ISO-8859-7', 'WINDOWS-1253'],
- alphabet=(u'αβγδεζηθικλμνξοπρσςτυφχψωάέήίόύώ'
- u'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΣΤΥΦΧΨΩΆΈΉΊΌΎΏ'),
- wiki_start_pages=[u'Πύλη:Κύρια']),
- 'English': Language(name='English',
- iso_code='en',
- use_ascii=True,
- charsets=['ISO-8859-1', 'WINDOWS-1252'],
- wiki_start_pages=[u'Main_Page']),
- 'Esperanto': Language(name='Esperanto',
- iso_code='eo',
- # Q, W, X, and Y not used at all
- use_ascii=False,
- charsets=['ISO-8859-3'],
- alphabet=(u'abcĉdefgĝhĥijĵklmnoprsŝtuŭvz'
- u'ABCĈDEFGĜHĤIJĴKLMNOPRSŜTUŬVZ'),
- wiki_start_pages=[u'Vikipedio:Ĉefpaĝo']),
- 'Spanish': Language(name='Spanish',
- iso_code='es',
- use_ascii=True,
- charsets=['ISO-8859-1', 'ISO-8859-15',
- 'WINDOWS-1252'],
- alphabet=u'ñáéíóúüÑÁÉÍÓÚÜ',
- wiki_start_pages=[u'Wikipedia:Portada']),
- 'Estonian': Language(name='Estonian',
- iso_code='et',
- use_ascii=False,
- charsets=['ISO-8859-4', 'ISO-8859-13',
- 'WINDOWS-1257'],
- # C, F, Š, Q, W, X, Y, Z, Ž are only for
- # loanwords
- alphabet=(u'ABDEGHIJKLMNOPRSTUVÕÄÖÜ'
- u'abdeghijklmnoprstuvõäöü'),
- wiki_start_pages=[u'Esileht']),
- 'Finnish': Language(name='Finnish',
- iso_code='fi',
- use_ascii=True,
- charsets=['ISO-8859-1', 'ISO-8859-15',
- 'WINDOWS-1252'],
- alphabet=u'ÅÄÖŠŽåäöšž',
- wiki_start_pages=[u'Wikipedia:Etusivu']),
- 'French': Language(name='French',
- iso_code='fr',
- use_ascii=True,
- charsets=['ISO-8859-1', 'ISO-8859-15',
- 'WINDOWS-1252'],
- alphabet=u'œàâçèéîïùûêŒÀÂÇÈÉÎÏÙÛÊ',
- wiki_start_pages=[u'Wikipédia:Accueil_principal',
- u'Bœuf (animal)']),
- 'Hebrew': Language(name='Hebrew',
- iso_code='he',
- use_ascii=False,
- charsets=['ISO-8859-8', 'WINDOWS-1255'],
- alphabet=u'אבגדהוזחטיךכלםמןנסעףפץצקרשתװױײ',
- wiki_start_pages=[u'עמוד_ראשי']),
- 'Croatian': Language(name='Croatian',
- iso_code='hr',
- # Q, W, X, Y are only used for foreign words.
- use_ascii=False,
- charsets=['ISO-8859-2', 'WINDOWS-1250'],
- alphabet=(u'abcčćdđefghijklmnoprsštuvzž'
- u'ABCČĆDĐEFGHIJKLMNOPRSŠTUVZŽ'),
- wiki_start_pages=[u'Glavna_stranica']),
- 'Hungarian': Language(name='Hungarian',
- iso_code='hu',
- # Q, W, X, Y are only used for foreign words.
- use_ascii=False,
- charsets=['ISO-8859-2', 'WINDOWS-1250'],
- alphabet=(u'abcdefghijklmnoprstuvzáéíóöőúüű'
- u'ABCDEFGHIJKLMNOPRSTUVZÁÉÍÓÖŐÚÜŰ'),
- wiki_start_pages=[u'Kezdőlap']),
- 'Italian': Language(name='Italian',
- iso_code='it',
- use_ascii=True,
- charsets=['ISO-8859-1', 'ISO-8859-15',
- 'WINDOWS-1252'],
- alphabet=u'ÀÈÉÌÒÓÙàèéìòóù',
- wiki_start_pages=[u'Pagina_principale']),
- 'Lithuanian': Language(name='Lithuanian',
- iso_code='lt',
- use_ascii=False,
- charsets=['ISO-8859-13', 'WINDOWS-1257',
- 'ISO-8859-4'],
- # Q, W, and X not used at all
- alphabet=(u'AĄBCČDEĘĖFGHIĮYJKLMNOPRSŠTUŲŪVZŽ'
- u'aąbcčdeęėfghiįyjklmnoprsštuųūvzž'),
- wiki_start_pages=[u'Pagrindinis_puslapis']),
- 'Latvian': Language(name='Latvian',
- iso_code='lv',
- use_ascii=False,
- charsets=['ISO-8859-13', 'WINDOWS-1257',
- 'ISO-8859-4'],
- # Q, W, X, Y are only for loanwords
- alphabet=(u'AĀBCČDEĒFGĢHIĪJKĶLĻMNŅOPRSŠTUŪVZŽ'
- u'aābcčdeēfgģhiījkķlļmnņoprsštuūvzž'),
- wiki_start_pages=[u'Sākumlapa']),
- 'Macedonian': Language(name='Macedonian',
- iso_code='mk',
- use_ascii=False,
- charsets=['ISO-8859-5', 'WINDOWS-1251',
- 'MacCyrillic', 'IBM855'],
- alphabet=(u'АБВГДЃЕЖЗЅИЈКЛЉМНЊОПРСТЌУФХЦЧЏШ'
- u'абвгдѓежзѕијклљмнњопрстќуфхцчџш'),
- wiki_start_pages=[u'Главна_страница']),
- 'Dutch': Language(name='Dutch',
- iso_code='nl',
- use_ascii=True,
- charsets=['ISO-8859-1', 'WINDOWS-1252'],
- wiki_start_pages=[u'Hoofdpagina']),
- 'Polish': Language(name='Polish',
- iso_code='pl',
- # Q and X are only used for foreign words.
- use_ascii=False,
- charsets=['ISO-8859-2', 'WINDOWS-1250'],
- alphabet=(u'AĄBCĆDEĘFGHIJKLŁMNŃOÓPRSŚTUWYZŹŻ'
- u'aąbcćdeęfghijklłmnńoóprsśtuwyzźż'),
- wiki_start_pages=[u'Wikipedia:Strona_główna']),
- 'Portuguese': Language(name='Portuguese',
- iso_code='pt',
- use_ascii=True,
- charsets=['ISO-8859-1', 'ISO-8859-15',
- 'WINDOWS-1252'],
- alphabet=u'ÁÂÃÀÇÉÊÍÓÔÕÚáâãàçéêíóôõú',
- wiki_start_pages=[u'Wikipédia:Página_principal']),
- 'Romanian': Language(name='Romanian',
- iso_code='ro',
- use_ascii=True,
- charsets=['ISO-8859-2', 'WINDOWS-1250'],
- alphabet=u'ăâîșțĂÂÎȘȚ',
- wiki_start_pages=[u'Pagina_principală']),
- 'Russian': Language(name='Russian',
- iso_code='ru',
- use_ascii=False,
- charsets=['ISO-8859-5', 'WINDOWS-1251',
- 'KOI8-R', 'MacCyrillic', 'IBM866',
- 'IBM855'],
- alphabet=(u'абвгдеёжзийклмнопрстуфхцчшщъыьэюя'
- u'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'),
- wiki_start_pages=[u'Заглавная_страница']),
- 'Slovak': Language(name='Slovak',
- iso_code='sk',
- use_ascii=True,
- charsets=['ISO-8859-2', 'WINDOWS-1250'],
- alphabet=u'áäčďéíĺľňóôŕšťúýžÁÄČĎÉÍĹĽŇÓÔŔŠŤÚÝŽ',
- wiki_start_pages=[u'Hlavná_stránka']),
- 'Slovene': Language(name='Slovene',
- iso_code='sl',
- # Q, W, X, Y are only used for foreign words.
- use_ascii=False,
- charsets=['ISO-8859-2', 'WINDOWS-1250'],
- alphabet=(u'abcčdefghijklmnoprsštuvzž'
- u'ABCČDEFGHIJKLMNOPRSŠTUVZŽ'),
- wiki_start_pages=[u'Glavna_stran']),
- # Serbian can be written in both Latin and Cyrillic, but there's no
- # simple way to get the Latin alphabet pages from Wikipedia through
- # the API, so for now we just support Cyrillic.
- 'Serbian': Language(name='Serbian',
- iso_code='sr',
- alphabet=(u'АБВГДЂЕЖЗИЈКЛЉМНЊОПРСТЋУФХЦЧЏШ'
- u'абвгдђежзијклљмнњопрстћуфхцчџш'),
- charsets=['ISO-8859-5', 'WINDOWS-1251',
- 'MacCyrillic', 'IBM855'],
- wiki_start_pages=[u'Главна_страна']),
- 'Thai': Language(name='Thai',
- iso_code='th',
- use_ascii=False,
- charsets=['ISO-8859-11', 'TIS-620', 'CP874'],
- alphabet=u'กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛',
- wiki_start_pages=[u'หน้าหลัก']),
- 'Turkish': Language(name='Turkish',
- iso_code='tr',
- # Q, W, and X are not used by Turkish
- use_ascii=False,
- charsets=['ISO-8859-3', 'ISO-8859-9',
- 'WINDOWS-1254'],
- alphabet=(u'abcçdefgğhıijklmnoöprsştuüvyzâîû'
- u'ABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZÂÎÛ'),
- wiki_start_pages=[u'Ana_Sayfa']),
- 'Vietnamese': Language(name='Vietnamese',
- iso_code='vi',
- use_ascii=False,
- # Windows-1258 is the only common 8-bit
- # Vietnamese encoding supported by Python.
- # From Wikipedia:
- # For systems that lack support for Unicode,
- # dozens of 8-bit Vietnamese code pages are
- # available.[1] The most common are VISCII
- # (TCVN 5712:1993), VPS, and Windows-1258.[3]
- # Where ASCII is required, such as when
- # ensuring readability in plain text e-mail,
- # Vietnamese letters are often encoded
- # according to Vietnamese Quoted-Readable
- # (VIQR) or VSCII Mnemonic (VSCII-MNEM),[4]
- # though usage of either variable-width
- # scheme has declined dramatically following
- # the adoption of Unicode on the World Wide
- # Web.
- charsets=['WINDOWS-1258'],
- alphabet=(u'aăâbcdđeêghiklmnoôơpqrstuưvxy'
- u'AĂÂBCDĐEÊGHIKLMNOÔƠPQRSTUƯVXY'),
- wiki_start_pages=[u'Chữ_Quốc_ngữ']),
- }
+LANGUAGES = {
+ "Arabic": Language(
+ name="Arabic",
+ iso_code="ar",
+ use_ascii=False,
+ # We only support encodings that use isolated
+ # forms, because the current recommendation is
+ # that the rendering system handles presentation
+ # forms. This means we purposefully skip IBM864.
+ charsets=["ISO-8859-6", "WINDOWS-1256", "CP720", "CP864"],
+ alphabet="ءآأؤإئابةتثجحخدذرزسشصضطظعغػؼؽؾؿـفقكلمنهوىيًٌٍَُِّ",
+ wiki_start_pages=["الصفحة_الرئيسية"],
+ ),
+ "Belarusian": Language(
+ name="Belarusian",
+ iso_code="be",
+ use_ascii=False,
+ charsets=["ISO-8859-5", "WINDOWS-1251", "IBM866", "MacCyrillic"],
+ alphabet="АБВГДЕЁЖЗІЙКЛМНОПРСТУЎФХЦЧШЫЬЭЮЯабвгдеёжзійклмнопрстуўфхцчшыьэюяʼ",
+ wiki_start_pages=["Галоўная_старонка"],
+ ),
+ "Bulgarian": Language(
+ name="Bulgarian",
+ iso_code="bg",
+ use_ascii=False,
+ charsets=["ISO-8859-5", "WINDOWS-1251", "IBM855"],
+ alphabet="АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯабвгдежзийклмнопрстуфхцчшщъьюя",
+ wiki_start_pages=["Начална_страница"],
+ ),
+ "Czech": Language(
+ name="Czech",
+ iso_code="cz",
+ use_ascii=True,
+ charsets=["ISO-8859-2", "WINDOWS-1250"],
+ alphabet="áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ",
+ wiki_start_pages=["Hlavní_strana"],
+ ),
+ "Danish": Language(
+ name="Danish",
+ iso_code="da",
+ use_ascii=True,
+ charsets=["ISO-8859-1", "ISO-8859-15", "WINDOWS-1252", "MacRoman"],
+ alphabet="æøåÆØÅ",
+ wiki_start_pages=["Forside"],
+ ),
+ "German": Language(
+ name="German",
+ iso_code="de",
+ use_ascii=True,
+ charsets=["ISO-8859-1", "ISO-8859-15", "WINDOWS-1252", "MacRoman"],
+ alphabet="äöüßẞÄÖÜ",
+ wiki_start_pages=["Wikipedia:Hauptseite"],
+ ),
+ "Greek": Language(
+ name="Greek",
+ iso_code="el",
+ use_ascii=False,
+ charsets=["ISO-8859-7", "WINDOWS-1253"],
+ alphabet="αβγδεζηθικλμνξοπρσςτυφχψωάέήίόύώΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΣΤΥΦΧΨΩΆΈΉΊΌΎΏ",
+ wiki_start_pages=["Πύλη:Κύρια"],
+ ),
+ "English": Language(
+ name="English",
+ iso_code="en",
+ use_ascii=True,
+ charsets=["ISO-8859-1", "WINDOWS-1252", "MacRoman"],
+ wiki_start_pages=["Main_Page"],
+ ),
+ "Esperanto": Language(
+ name="Esperanto",
+ iso_code="eo",
+ # Q, W, X, and Y not used at all
+ use_ascii=False,
+ charsets=["ISO-8859-3"],
+ alphabet="abcĉdefgĝhĥijĵklmnoprsŝtuŭvzABCĈDEFGĜHĤIJĴKLMNOPRSŜTUŬVZ",
+ wiki_start_pages=["Vikipedio:Ĉefpaĝo"],
+ ),
+ "Spanish": Language(
+ name="Spanish",
+ iso_code="es",
+ use_ascii=True,
+ charsets=["ISO-8859-1", "ISO-8859-15", "WINDOWS-1252", "MacRoman"],
+ alphabet="ñáéíóúüÑÁÉÍÓÚÜ",
+ wiki_start_pages=["Wikipedia:Portada"],
+ ),
+ "Estonian": Language(
+ name="Estonian",
+ iso_code="et",
+ use_ascii=False,
+ charsets=["ISO-8859-4", "ISO-8859-13", "WINDOWS-1257"],
+ # C, F, Š, Q, W, X, Y, Z, Ž are only for
+ # loanwords
+ alphabet="ABDEGHIJKLMNOPRSTUVÕÄÖÜabdeghijklmnoprstuvõäöü",
+ wiki_start_pages=["Esileht"],
+ ),
+ "Finnish": Language(
+ name="Finnish",
+ iso_code="fi",
+ use_ascii=True,
+ charsets=["ISO-8859-1", "ISO-8859-15", "WINDOWS-1252", "MacRoman"],
+ alphabet="ÅÄÖŠŽåäöšž",
+ wiki_start_pages=["Wikipedia:Etusivu"],
+ ),
+ "French": Language(
+ name="French",
+ iso_code="fr",
+ use_ascii=True,
+ charsets=["ISO-8859-1", "ISO-8859-15", "WINDOWS-1252", "MacRoman"],
+ alphabet="œàâçèéîïùûêŒÀÂÇÈÉÎÏÙÛÊ",
+ wiki_start_pages=["Wikipédia:Accueil_principal", "Bœuf (animal)"],
+ ),
+ "Hebrew": Language(
+ name="Hebrew",
+ iso_code="he",
+ use_ascii=False,
+ charsets=["ISO-8859-8", "WINDOWS-1255"],
+ alphabet="אבגדהוזחטיךכלםמןנסעףפץצקרשתװױײ",
+ wiki_start_pages=["עמוד_ראשי"],
+ ),
+ "Croatian": Language(
+ name="Croatian",
+ iso_code="hr",
+ # Q, W, X, Y are only used for foreign words.
+ use_ascii=False,
+ charsets=["ISO-8859-2", "WINDOWS-1250"],
+ alphabet="abcčćdđefghijklmnoprsštuvzžABCČĆDĐEFGHIJKLMNOPRSŠTUVZŽ",
+ wiki_start_pages=["Glavna_stranica"],
+ ),
+ "Hungarian": Language(
+ name="Hungarian",
+ iso_code="hu",
+ # Q, W, X, Y are only used for foreign words.
+ use_ascii=False,
+ charsets=["ISO-8859-2", "WINDOWS-1250"],
+ alphabet="abcdefghijklmnoprstuvzáéíóöőúüűABCDEFGHIJKLMNOPRSTUVZÁÉÍÓÖŐÚÜŰ",
+ wiki_start_pages=["Kezdőlap"],
+ ),
+ "Italian": Language(
+ name="Italian",
+ iso_code="it",
+ use_ascii=True,
+ charsets=["ISO-8859-1", "ISO-8859-15", "WINDOWS-1252", "MacRoman"],
+ alphabet="ÀÈÉÌÒÓÙàèéìòóù",
+ wiki_start_pages=["Pagina_principale"],
+ ),
+ "Lithuanian": Language(
+ name="Lithuanian",
+ iso_code="lt",
+ use_ascii=False,
+ charsets=["ISO-8859-13", "WINDOWS-1257", "ISO-8859-4"],
+ # Q, W, and X not used at all
+ alphabet="AĄBCČDEĘĖFGHIĮYJKLMNOPRSŠTUŲŪVZŽaąbcčdeęėfghiįyjklmnoprsštuųūvzž",
+ wiki_start_pages=["Pagrindinis_puslapis"],
+ ),
+ "Latvian": Language(
+ name="Latvian",
+ iso_code="lv",
+ use_ascii=False,
+ charsets=["ISO-8859-13", "WINDOWS-1257", "ISO-8859-4"],
+ # Q, W, X, Y are only for loanwords
+ alphabet="AĀBCČDEĒFGĢHIĪJKĶLĻMNŅOPRSŠTUŪVZŽaābcčdeēfgģhiījkķlļmnņoprsštuūvzž",
+ wiki_start_pages=["Sākumlapa"],
+ ),
+ "Macedonian": Language(
+ name="Macedonian",
+ iso_code="mk",
+ use_ascii=False,
+ charsets=["ISO-8859-5", "WINDOWS-1251", "MacCyrillic", "IBM855"],
+ alphabet="АБВГДЃЕЖЗЅИЈКЛЉМНЊОПРСТЌУФХЦЧЏШабвгдѓежзѕијклљмнњопрстќуфхцчџш",
+ wiki_start_pages=["Главна_страница"],
+ ),
+ "Dutch": Language(
+ name="Dutch",
+ iso_code="nl",
+ use_ascii=True,
+ charsets=["ISO-8859-1", "WINDOWS-1252", "MacRoman"],
+ wiki_start_pages=["Hoofdpagina"],
+ ),
+ "Polish": Language(
+ name="Polish",
+ iso_code="pl",
+ # Q and X are only used for foreign words.
+ use_ascii=False,
+ charsets=["ISO-8859-2", "WINDOWS-1250"],
+ alphabet="AĄBCĆDEĘFGHIJKLŁMNŃOÓPRSŚTUWYZŹŻaąbcćdeęfghijklłmnńoóprsśtuwyzźż",
+ wiki_start_pages=["Wikipedia:Strona_główna"],
+ ),
+ "Portuguese": Language(
+ name="Portuguese",
+ iso_code="pt",
+ use_ascii=True,
+ charsets=["ISO-8859-1", "ISO-8859-15", "WINDOWS-1252", "MacRoman"],
+ alphabet="ÁÂÃÀÇÉÊÍÓÔÕÚáâãàçéêíóôõú",
+ wiki_start_pages=["Wikipédia:Página_principal"],
+ ),
+ "Romanian": Language(
+ name="Romanian",
+ iso_code="ro",
+ use_ascii=True,
+ charsets=["ISO-8859-2", "WINDOWS-1250"],
+ alphabet="ăâîșțĂÂÎȘȚ",
+ wiki_start_pages=["Pagina_principală"],
+ ),
+ "Russian": Language(
+ name="Russian",
+ iso_code="ru",
+ use_ascii=False,
+ charsets=[
+ "ISO-8859-5",
+ "WINDOWS-1251",
+ "KOI8-R",
+ "MacCyrillic",
+ "IBM866",
+ "IBM855",
+ ],
+ alphabet="абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ",
+ wiki_start_pages=["Заглавная_страница"],
+ ),
+ "Slovak": Language(
+ name="Slovak",
+ iso_code="sk",
+ use_ascii=True,
+ charsets=["ISO-8859-2", "WINDOWS-1250"],
+ alphabet="áäčďéíĺľňóôŕšťúýžÁÄČĎÉÍĹĽŇÓÔŔŠŤÚÝŽ",
+ wiki_start_pages=["Hlavná_stránka"],
+ ),
+ "Slovene": Language(
+ name="Slovene",
+ iso_code="sl",
+ # Q, W, X, Y are only used for foreign words.
+ use_ascii=False,
+ charsets=["ISO-8859-2", "WINDOWS-1250"],
+ alphabet="abcčdefghijklmnoprsštuvzžABCČDEFGHIJKLMNOPRSŠTUVZŽ",
+ wiki_start_pages=["Glavna_stran"],
+ ),
+ # Serbian can be written in both Latin and Cyrillic, but there's no
+ # simple way to get the Latin alphabet pages from Wikipedia through
+ # the API, so for now we just support Cyrillic.
+ "Serbian": Language(
+ name="Serbian",
+ iso_code="sr",
+ alphabet="АБВГДЂЕЖЗИЈКЛЉМНЊОПРСТЋУФХЦЧЏШабвгдђежзијклљмнњопрстћуфхцчџш",
+ charsets=["ISO-8859-5", "WINDOWS-1251", "MacCyrillic", "IBM855"],
+ wiki_start_pages=["Главна_страна"],
+ ),
+ "Thai": Language(
+ name="Thai",
+ iso_code="th",
+ use_ascii=False,
+ charsets=["ISO-8859-11", "TIS-620", "CP874"],
+ alphabet="กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛",
+ wiki_start_pages=["หน้าหลัก"],
+ ),
+ "Turkish": Language(
+ name="Turkish",
+ iso_code="tr",
+ # Q, W, and X are not used by Turkish
+ use_ascii=False,
+ charsets=["ISO-8859-3", "ISO-8859-9", "WINDOWS-1254"],
+ alphabet="abcçdefgğhıijklmnoöprsştuüvyzâîûABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZÂÎÛ",
+ wiki_start_pages=["Ana_Sayfa"],
+ ),
+ "Vietnamese": Language(
+ name="Vietnamese",
+ iso_code="vi",
+ use_ascii=False,
+ # Windows-1258 is the only common 8-bit
+ # Vietnamese encoding supported by Python.
+ # From Wikipedia:
+ # For systems that lack support for Unicode,
+ # dozens of 8-bit Vietnamese code pages are
+ # available.[1] The most common are VISCII
+ # (TCVN 5712:1993), VPS, and Windows-1258.[3]
+ # Where ASCII is required, such as when
+ # ensuring readability in plain text e-mail,
+ # Vietnamese letters are often encoded
+ # according to Vietnamese Quoted-Readable
+ # (VIQR) or VSCII Mnemonic (VSCII-MNEM),[4]
+ # though usage of either variable-width
+ # scheme has declined dramatically following
+ # the adoption of Unicode on the World Wide
+ # Web.
+ charsets=["WINDOWS-1258"],
+ alphabet="aăâbcdđeêghiklmnoôơpqrstuưvxyAĂÂBCDĐEÊGHIKLMNOÔƠPQRSTUƯVXY",
+ wiki_start_pages=["Chữ_Quốc_ngữ"],
+ ),
+}
diff --git a/src/pip/_vendor/chardet/py.typed b/src/pip/_vendor/chardet/py.typed
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/pip/_vendor/chardet/py.typed
diff --git a/src/pip/_vendor/chardet/resultdict.py b/src/pip/_vendor/chardet/resultdict.py
new file mode 100644
index 000000000..7d36e64c4
--- /dev/null
+++ b/src/pip/_vendor/chardet/resultdict.py
@@ -0,0 +1,16 @@
+from typing import TYPE_CHECKING, Optional
+
+if TYPE_CHECKING:
+ # TypedDict was introduced in Python 3.8.
+ #
+ # TODO: Remove the else block and TYPE_CHECKING check when dropping support
+ # for Python 3.7.
+ from typing import TypedDict
+
+ class ResultDict(TypedDict):
+ encoding: Optional[str]
+ confidence: float
+ language: Optional[str]
+
+else:
+ ResultDict = dict
diff --git a/src/pip/_vendor/chardet/sbcharsetprober.py b/src/pip/_vendor/chardet/sbcharsetprober.py
index 46ba835c6..0ffbcdd2c 100644
--- a/src/pip/_vendor/chardet/sbcharsetprober.py
+++ b/src/pip/_vendor/chardet/sbcharsetprober.py
@@ -26,70 +26,77 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
-from collections import namedtuple
+from typing import Dict, List, NamedTuple, Optional, Union
from .charsetprober import CharSetProber
from .enums import CharacterCategory, ProbingState, SequenceLikelihood
-SingleByteCharSetModel = namedtuple('SingleByteCharSetModel',
- ['charset_name',
- 'language',
- 'char_to_order_map',
- 'language_model',
- 'typical_positive_ratio',
- 'keep_ascii_letters',
- 'alphabet'])
+class SingleByteCharSetModel(NamedTuple):
+ charset_name: str
+ language: str
+ char_to_order_map: Dict[int, int]
+ language_model: Dict[int, Dict[int, int]]
+ typical_positive_ratio: float
+ keep_ascii_letters: bool
+ alphabet: str
class SingleByteCharSetProber(CharSetProber):
SAMPLE_SIZE = 64
- SB_ENOUGH_REL_THRESHOLD = 1024 # 0.25 * SAMPLE_SIZE^2
+ SB_ENOUGH_REL_THRESHOLD = 1024 # 0.25 * SAMPLE_SIZE^2
POSITIVE_SHORTCUT_THRESHOLD = 0.95
NEGATIVE_SHORTCUT_THRESHOLD = 0.05
- def __init__(self, model, reversed=False, name_prober=None):
- super(SingleByteCharSetProber, self).__init__()
+ def __init__(
+ self,
+ model: SingleByteCharSetModel,
+ is_reversed: bool = False,
+ name_prober: Optional[CharSetProber] = None,
+ ) -> None:
+ super().__init__()
self._model = model
# TRUE if we need to reverse every pair in the model lookup
- self._reversed = reversed
+ self._reversed = is_reversed
# Optional auxiliary prober for name decision
self._name_prober = name_prober
- self._last_order = None
- self._seq_counters = None
- self._total_seqs = None
- self._total_char = None
- self._freq_char = None
+ self._last_order = 255
+ self._seq_counters: List[int] = []
+ self._total_seqs = 0
+ self._total_char = 0
+ self._control_char = 0
+ self._freq_char = 0
self.reset()
- def reset(self):
- super(SingleByteCharSetProber, self).reset()
+ def reset(self) -> None:
+ super().reset()
# char order of last character
self._last_order = 255
self._seq_counters = [0] * SequenceLikelihood.get_num_categories()
self._total_seqs = 0
self._total_char = 0
+ self._control_char = 0
# characters that fall in our sampling range
self._freq_char = 0
@property
- def charset_name(self):
+ def charset_name(self) -> Optional[str]:
if self._name_prober:
return self._name_prober.charset_name
- else:
- return self._model.charset_name
+ return self._model.charset_name
@property
- def language(self):
+ def language(self) -> Optional[str]:
if self._name_prober:
return self._name_prober.language
- else:
- return self._model.language
+ return self._model.language
- def feed(self, byte_str):
+ def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState:
# TODO: Make filter_international_words keep things in self.alphabet
if not self._model.keep_ascii_letters:
byte_str = self.filter_international_words(byte_str)
+ else:
+ byte_str = self.remove_xml_tags(byte_str)
if not byte_str:
return self.state
char_to_order_map = self._model.char_to_order_map
@@ -103,9 +110,6 @@ class SingleByteCharSetProber(CharSetProber):
# _total_char purposes.
if order < CharacterCategory.CONTROL:
self._total_char += 1
- # TODO: Follow uchardet's lead and discount confidence for frequent
- # control characters.
- # See https://github.com/BYVoid/uchardet/commit/55b4f23971db61
if order < self.SAMPLE_SIZE:
self._freq_char += 1
if self._last_order < self.SAMPLE_SIZE:
@@ -122,23 +126,36 @@ class SingleByteCharSetProber(CharSetProber):
if self._total_seqs > self.SB_ENOUGH_REL_THRESHOLD:
confidence = self.get_confidence()
if confidence > self.POSITIVE_SHORTCUT_THRESHOLD:
- self.logger.debug('%s confidence = %s, we have a winner',
- charset_name, confidence)
+ self.logger.debug(
+ "%s confidence = %s, we have a winner", charset_name, confidence
+ )
self._state = ProbingState.FOUND_IT
elif confidence < self.NEGATIVE_SHORTCUT_THRESHOLD:
- self.logger.debug('%s confidence = %s, below negative '
- 'shortcut threshhold %s', charset_name,
- confidence,
- self.NEGATIVE_SHORTCUT_THRESHOLD)
+ self.logger.debug(
+ "%s confidence = %s, below negative shortcut threshold %s",
+ charset_name,
+ confidence,
+ self.NEGATIVE_SHORTCUT_THRESHOLD,
+ )
self._state = ProbingState.NOT_ME
return self.state
- def get_confidence(self):
+ def get_confidence(self) -> float:
r = 0.01
if self._total_seqs > 0:
- r = ((1.0 * self._seq_counters[SequenceLikelihood.POSITIVE]) /
- self._total_seqs / self._model.typical_positive_ratio)
+ r = (
+ (
+ self._seq_counters[SequenceLikelihood.POSITIVE]
+ + 0.25 * self._seq_counters[SequenceLikelihood.LIKELY]
+ )
+ / self._total_seqs
+ / self._model.typical_positive_ratio
+ )
+ # The more control characters (proportionnaly to the size
+ # of the text), the less confident we become in the current
+ # charset.
+ r = r * (self._total_char - self._control_char) / self._total_char
r = r * self._freq_char / self._total_char
if r >= 1.0:
r = 0.99
diff --git a/src/pip/_vendor/chardet/sbcsgroupprober.py b/src/pip/_vendor/chardet/sbcsgroupprober.py
index bdeef4e15..890ae8465 100644
--- a/src/pip/_vendor/chardet/sbcsgroupprober.py
+++ b/src/pip/_vendor/chardet/sbcsgroupprober.py
@@ -28,33 +28,38 @@
from .charsetgroupprober import CharSetGroupProber
from .hebrewprober import HebrewProber
-from .langbulgarianmodel import (ISO_8859_5_BULGARIAN_MODEL,
- WINDOWS_1251_BULGARIAN_MODEL)
+from .langbulgarianmodel import ISO_8859_5_BULGARIAN_MODEL, WINDOWS_1251_BULGARIAN_MODEL
from .langgreekmodel import ISO_8859_7_GREEK_MODEL, WINDOWS_1253_GREEK_MODEL
from .langhebrewmodel import WINDOWS_1255_HEBREW_MODEL
+
# from .langhungarianmodel import (ISO_8859_2_HUNGARIAN_MODEL,
# WINDOWS_1250_HUNGARIAN_MODEL)
-from .langrussianmodel import (IBM855_RUSSIAN_MODEL, IBM866_RUSSIAN_MODEL,
- ISO_8859_5_RUSSIAN_MODEL, KOI8_R_RUSSIAN_MODEL,
- MACCYRILLIC_RUSSIAN_MODEL,
- WINDOWS_1251_RUSSIAN_MODEL)
+from .langrussianmodel import (
+ IBM855_RUSSIAN_MODEL,
+ IBM866_RUSSIAN_MODEL,
+ ISO_8859_5_RUSSIAN_MODEL,
+ KOI8_R_RUSSIAN_MODEL,
+ MACCYRILLIC_RUSSIAN_MODEL,
+ WINDOWS_1251_RUSSIAN_MODEL,
+)
from .langthaimodel import TIS_620_THAI_MODEL
from .langturkishmodel import ISO_8859_9_TURKISH_MODEL
from .sbcharsetprober import SingleByteCharSetProber
class SBCSGroupProber(CharSetGroupProber):
- def __init__(self):
- super(SBCSGroupProber, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
hebrew_prober = HebrewProber()
- logical_hebrew_prober = SingleByteCharSetProber(WINDOWS_1255_HEBREW_MODEL,
- False, hebrew_prober)
+ logical_hebrew_prober = SingleByteCharSetProber(
+ WINDOWS_1255_HEBREW_MODEL, is_reversed=False, name_prober=hebrew_prober
+ )
# TODO: See if using ISO-8859-8 Hebrew model works better here, since
# it's actually the visual one
- visual_hebrew_prober = SingleByteCharSetProber(WINDOWS_1255_HEBREW_MODEL,
- True, hebrew_prober)
- hebrew_prober.set_model_probers(logical_hebrew_prober,
- visual_hebrew_prober)
+ visual_hebrew_prober = SingleByteCharSetProber(
+ WINDOWS_1255_HEBREW_MODEL, is_reversed=True, name_prober=hebrew_prober
+ )
+ hebrew_prober.set_model_probers(logical_hebrew_prober, visual_hebrew_prober)
# TODO: ORDER MATTERS HERE. I changed the order vs what was in master
# and several tests failed that did not before. Some thought
# should be put into the ordering, and we should consider making
diff --git a/src/pip/_vendor/chardet/sjisprober.py b/src/pip/_vendor/chardet/sjisprober.py
index 9e29623bd..91df07796 100644
--- a/src/pip/_vendor/chardet/sjisprober.py
+++ b/src/pip/_vendor/chardet/sjisprober.py
@@ -25,68 +25,81 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
-from .mbcharsetprober import MultiByteCharSetProber
-from .codingstatemachine import CodingStateMachine
+from typing import Union
+
from .chardistribution import SJISDistributionAnalysis
+from .codingstatemachine import CodingStateMachine
+from .enums import MachineState, ProbingState
from .jpcntx import SJISContextAnalysis
+from .mbcharsetprober import MultiByteCharSetProber
from .mbcssm import SJIS_SM_MODEL
-from .enums import ProbingState, MachineState
class SJISProber(MultiByteCharSetProber):
- def __init__(self):
- super(SJISProber, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
self.coding_sm = CodingStateMachine(SJIS_SM_MODEL)
self.distribution_analyzer = SJISDistributionAnalysis()
self.context_analyzer = SJISContextAnalysis()
self.reset()
- def reset(self):
- super(SJISProber, self).reset()
+ def reset(self) -> None:
+ super().reset()
self.context_analyzer.reset()
@property
- def charset_name(self):
+ def charset_name(self) -> str:
return self.context_analyzer.charset_name
@property
- def language(self):
+ def language(self) -> str:
return "Japanese"
- def feed(self, byte_str):
- for i in range(len(byte_str)):
- coding_state = self.coding_sm.next_state(byte_str[i])
+ def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState:
+ assert self.coding_sm is not None
+ assert self.distribution_analyzer is not None
+
+ for i, byte in enumerate(byte_str):
+ coding_state = self.coding_sm.next_state(byte)
if coding_state == MachineState.ERROR:
- self.logger.debug('%s %s prober hit error at byte %s',
- self.charset_name, self.language, i)
+ self.logger.debug(
+ "%s %s prober hit error at byte %s",
+ self.charset_name,
+ self.language,
+ i,
+ )
self._state = ProbingState.NOT_ME
break
- elif coding_state == MachineState.ITS_ME:
+ if coding_state == MachineState.ITS_ME:
self._state = ProbingState.FOUND_IT
break
- elif coding_state == MachineState.START:
+ if coding_state == MachineState.START:
char_len = self.coding_sm.get_current_charlen()
if i == 0:
- self._last_char[1] = byte_str[0]
- self.context_analyzer.feed(self._last_char[2 - char_len:],
- char_len)
+ self._last_char[1] = byte
+ self.context_analyzer.feed(
+ self._last_char[2 - char_len :], char_len
+ )
self.distribution_analyzer.feed(self._last_char, char_len)
else:
- self.context_analyzer.feed(byte_str[i + 1 - char_len:i + 3
- - char_len], char_len)
- self.distribution_analyzer.feed(byte_str[i - 1:i + 1],
- char_len)
+ self.context_analyzer.feed(
+ byte_str[i + 1 - char_len : i + 3 - char_len], char_len
+ )
+ self.distribution_analyzer.feed(byte_str[i - 1 : i + 1], char_len)
self._last_char[0] = byte_str[-1]
if self.state == ProbingState.DETECTING:
- if (self.context_analyzer.got_enough_data() and
- (self.get_confidence() > self.SHORTCUT_THRESHOLD)):
+ if self.context_analyzer.got_enough_data() and (
+ self.get_confidence() > self.SHORTCUT_THRESHOLD
+ ):
self._state = ProbingState.FOUND_IT
return self.state
- def get_confidence(self):
+ def get_confidence(self) -> float:
+ assert self.distribution_analyzer is not None
+
context_conf = self.context_analyzer.get_confidence()
distrib_conf = self.distribution_analyzer.get_confidence()
return max(context_conf, distrib_conf)
diff --git a/src/pip/_vendor/chardet/universaldetector.py b/src/pip/_vendor/chardet/universaldetector.py
index 055a8ac1b..30c441dc2 100644
--- a/src/pip/_vendor/chardet/universaldetector.py
+++ b/src/pip/_vendor/chardet/universaldetector.py
@@ -39,16 +39,21 @@ class a user of ``chardet`` should use.
import codecs
import logging
import re
+from typing import List, Optional, Union
from .charsetgroupprober import CharSetGroupProber
+from .charsetprober import CharSetProber
from .enums import InputState, LanguageFilter, ProbingState
from .escprober import EscCharSetProber
from .latin1prober import Latin1Prober
+from .macromanprober import MacRomanProber
from .mbcsgroupprober import MBCSGroupProber
+from .resultdict import ResultDict
from .sbcsgroupprober import SBCSGroupProber
+from .utf1632prober import UTF1632Prober
-class UniversalDetector(object):
+class UniversalDetector:
"""
The ``UniversalDetector`` class underlies the ``chardet.detect`` function
and coordinates all of the different charset probers.
@@ -66,49 +71,87 @@ class UniversalDetector(object):
"""
MINIMUM_THRESHOLD = 0.20
- HIGH_BYTE_DETECTOR = re.compile(b'[\x80-\xFF]')
- ESC_DETECTOR = re.compile(b'(\033|~{)')
- WIN_BYTE_DETECTOR = re.compile(b'[\x80-\x9F]')
- ISO_WIN_MAP = {'iso-8859-1': 'Windows-1252',
- 'iso-8859-2': 'Windows-1250',
- 'iso-8859-5': 'Windows-1251',
- 'iso-8859-6': 'Windows-1256',
- 'iso-8859-7': 'Windows-1253',
- 'iso-8859-8': 'Windows-1255',
- 'iso-8859-9': 'Windows-1254',
- 'iso-8859-13': 'Windows-1257'}
-
- def __init__(self, lang_filter=LanguageFilter.ALL):
- self._esc_charset_prober = None
- self._charset_probers = []
- self.result = None
- self.done = None
- self._got_data = None
- self._input_state = None
- self._last_char = None
+ HIGH_BYTE_DETECTOR = re.compile(b"[\x80-\xFF]")
+ ESC_DETECTOR = re.compile(b"(\033|~{)")
+ WIN_BYTE_DETECTOR = re.compile(b"[\x80-\x9F]")
+ ISO_WIN_MAP = {
+ "iso-8859-1": "Windows-1252",
+ "iso-8859-2": "Windows-1250",
+ "iso-8859-5": "Windows-1251",
+ "iso-8859-6": "Windows-1256",
+ "iso-8859-7": "Windows-1253",
+ "iso-8859-8": "Windows-1255",
+ "iso-8859-9": "Windows-1254",
+ "iso-8859-13": "Windows-1257",
+ }
+ # Based on https://encoding.spec.whatwg.org/#names-and-labels
+ # but altered to match Python names for encodings and remove mappings
+ # that break tests.
+ LEGACY_MAP = {
+ "ascii": "Windows-1252",
+ "iso-8859-1": "Windows-1252",
+ "tis-620": "ISO-8859-11",
+ "iso-8859-9": "Windows-1254",
+ "gb2312": "GB18030",
+ "euc-kr": "CP949",
+ "utf-16le": "UTF-16",
+ }
+
+ def __init__(
+ self,
+ lang_filter: LanguageFilter = LanguageFilter.ALL,
+ should_rename_legacy: bool = False,
+ ) -> None:
+ self._esc_charset_prober: Optional[EscCharSetProber] = None
+ self._utf1632_prober: Optional[UTF1632Prober] = None
+ self._charset_probers: List[CharSetProber] = []
+ self.result: ResultDict = {
+ "encoding": None,
+ "confidence": 0.0,
+ "language": None,
+ }
+ self.done = False
+ self._got_data = False
+ self._input_state = InputState.PURE_ASCII
+ self._last_char = b""
self.lang_filter = lang_filter
self.logger = logging.getLogger(__name__)
- self._has_win_bytes = None
+ self._has_win_bytes = False
+ self.should_rename_legacy = should_rename_legacy
self.reset()
- def reset(self):
+ @property
+ def input_state(self) -> int:
+ return self._input_state
+
+ @property
+ def has_win_bytes(self) -> bool:
+ return self._has_win_bytes
+
+ @property
+ def charset_probers(self) -> List[CharSetProber]:
+ return self._charset_probers
+
+ def reset(self) -> None:
"""
Reset the UniversalDetector and all of its probers back to their
initial states. This is called by ``__init__``, so you only need to
call this directly in between analyses of different documents.
"""
- self.result = {'encoding': None, 'confidence': 0.0, 'language': None}
+ self.result = {"encoding": None, "confidence": 0.0, "language": None}
self.done = False
self._got_data = False
self._has_win_bytes = False
self._input_state = InputState.PURE_ASCII
- self._last_char = b''
+ self._last_char = b""
if self._esc_charset_prober:
self._esc_charset_prober.reset()
+ if self._utf1632_prober:
+ self._utf1632_prober.reset()
for prober in self._charset_probers:
prober.reset()
- def feed(self, byte_str):
+ def feed(self, byte_str: Union[bytes, bytearray]) -> None:
"""
Takes a chunk of a document and feeds it through all of the relevant
charset probers.
@@ -125,7 +168,7 @@ class UniversalDetector(object):
if self.done:
return
- if not len(byte_str):
+ if not byte_str:
return
if not isinstance(byte_str, bytearray):
@@ -136,35 +179,38 @@ class UniversalDetector(object):
# If the data starts with BOM, we know it is UTF
if byte_str.startswith(codecs.BOM_UTF8):
# EF BB BF UTF-8 with BOM
- self.result = {'encoding': "UTF-8-SIG",
- 'confidence': 1.0,
- 'language': ''}
- elif byte_str.startswith((codecs.BOM_UTF32_LE,
- codecs.BOM_UTF32_BE)):
+ self.result = {
+ "encoding": "UTF-8-SIG",
+ "confidence": 1.0,
+ "language": "",
+ }
+ elif byte_str.startswith((codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE)):
# FF FE 00 00 UTF-32, little-endian BOM
# 00 00 FE FF UTF-32, big-endian BOM
- self.result = {'encoding': "UTF-32",
- 'confidence': 1.0,
- 'language': ''}
- elif byte_str.startswith(b'\xFE\xFF\x00\x00'):
+ self.result = {"encoding": "UTF-32", "confidence": 1.0, "language": ""}
+ elif byte_str.startswith(b"\xFE\xFF\x00\x00"):
# FE FF 00 00 UCS-4, unusual octet order BOM (3412)
- self.result = {'encoding': "X-ISO-10646-UCS-4-3412",
- 'confidence': 1.0,
- 'language': ''}
- elif byte_str.startswith(b'\x00\x00\xFF\xFE'):
+ self.result = {
+ # TODO: This encoding is not supported by Python. Should remove?
+ "encoding": "X-ISO-10646-UCS-4-3412",
+ "confidence": 1.0,
+ "language": "",
+ }
+ elif byte_str.startswith(b"\x00\x00\xFF\xFE"):
# 00 00 FF FE UCS-4, unusual octet order BOM (2143)
- self.result = {'encoding': "X-ISO-10646-UCS-4-2143",
- 'confidence': 1.0,
- 'language': ''}
+ self.result = {
+ # TODO: This encoding is not supported by Python. Should remove?
+ "encoding": "X-ISO-10646-UCS-4-2143",
+ "confidence": 1.0,
+ "language": "",
+ }
elif byte_str.startswith((codecs.BOM_LE, codecs.BOM_BE)):
# FF FE UTF-16, little endian BOM
# FE FF UTF-16, big endian BOM
- self.result = {'encoding': "UTF-16",
- 'confidence': 1.0,
- 'language': ''}
+ self.result = {"encoding": "UTF-16", "confidence": 1.0, "language": ""}
self._got_data = True
- if self.result['encoding'] is not None:
+ if self.result["encoding"] is not None:
self.done = True
return
@@ -173,12 +219,29 @@ class UniversalDetector(object):
if self._input_state == InputState.PURE_ASCII:
if self.HIGH_BYTE_DETECTOR.search(byte_str):
self._input_state = InputState.HIGH_BYTE
- elif self._input_state == InputState.PURE_ASCII and \
- self.ESC_DETECTOR.search(self._last_char + byte_str):
+ elif (
+ self._input_state == InputState.PURE_ASCII
+ and self.ESC_DETECTOR.search(self._last_char + byte_str)
+ ):
self._input_state = InputState.ESC_ASCII
self._last_char = byte_str[-1:]
+ # next we will look to see if it is appears to be either a UTF-16 or
+ # UTF-32 encoding
+ if not self._utf1632_prober:
+ self._utf1632_prober = UTF1632Prober()
+
+ if self._utf1632_prober.state == ProbingState.DETECTING:
+ if self._utf1632_prober.feed(byte_str) == ProbingState.FOUND_IT:
+ self.result = {
+ "encoding": self._utf1632_prober.charset_name,
+ "confidence": self._utf1632_prober.get_confidence(),
+ "language": "",
+ }
+ self.done = True
+ return
+
# If we've seen escape sequences, use the EscCharSetProber, which
# uses a simple state machine to check for known escape sequences in
# HZ and ISO-2022 encodings, since those are the only encodings that
@@ -187,12 +250,11 @@ class UniversalDetector(object):
if not self._esc_charset_prober:
self._esc_charset_prober = EscCharSetProber(self.lang_filter)
if self._esc_charset_prober.feed(byte_str) == ProbingState.FOUND_IT:
- self.result = {'encoding':
- self._esc_charset_prober.charset_name,
- 'confidence':
- self._esc_charset_prober.get_confidence(),
- 'language':
- self._esc_charset_prober.language}
+ self.result = {
+ "encoding": self._esc_charset_prober.charset_name,
+ "confidence": self._esc_charset_prober.get_confidence(),
+ "language": self._esc_charset_prober.language,
+ }
self.done = True
# If we've seen high bytes (i.e., those with values greater than 127),
# we need to do more complicated checks using all our multi-byte and
@@ -207,17 +269,20 @@ class UniversalDetector(object):
if self.lang_filter & LanguageFilter.NON_CJK:
self._charset_probers.append(SBCSGroupProber())
self._charset_probers.append(Latin1Prober())
+ self._charset_probers.append(MacRomanProber())
for prober in self._charset_probers:
if prober.feed(byte_str) == ProbingState.FOUND_IT:
- self.result = {'encoding': prober.charset_name,
- 'confidence': prober.get_confidence(),
- 'language': prober.language}
+ self.result = {
+ "encoding": prober.charset_name,
+ "confidence": prober.get_confidence(),
+ "language": prober.language,
+ }
self.done = True
break
if self.WIN_BYTE_DETECTOR.search(byte_str):
self._has_win_bytes = True
- def close(self):
+ def close(self) -> ResultDict:
"""
Stop analyzing the current document and come up with a final
prediction.
@@ -231,13 +296,11 @@ class UniversalDetector(object):
self.done = True
if not self._got_data:
- self.logger.debug('no data received!')
+ self.logger.debug("no data received!")
# Default to ASCII if it is all we've seen so far
elif self._input_state == InputState.PURE_ASCII:
- self.result = {'encoding': 'ascii',
- 'confidence': 1.0,
- 'language': ''}
+ self.result = {"encoding": "ascii", "confidence": 1.0, "language": ""}
# If we have seen non-ASCII, return the best that met MINIMUM_THRESHOLD
elif self._input_state == InputState.HIGH_BYTE:
@@ -253,34 +316,47 @@ class UniversalDetector(object):
max_prober = prober
if max_prober and (max_prober_confidence > self.MINIMUM_THRESHOLD):
charset_name = max_prober.charset_name
- lower_charset_name = max_prober.charset_name.lower()
+ assert charset_name is not None
+ lower_charset_name = charset_name.lower()
confidence = max_prober.get_confidence()
# Use Windows encoding name instead of ISO-8859 if we saw any
# extra Windows-specific bytes
- if lower_charset_name.startswith('iso-8859'):
+ if lower_charset_name.startswith("iso-8859"):
if self._has_win_bytes:
- charset_name = self.ISO_WIN_MAP.get(lower_charset_name,
- charset_name)
- self.result = {'encoding': charset_name,
- 'confidence': confidence,
- 'language': max_prober.language}
+ charset_name = self.ISO_WIN_MAP.get(
+ lower_charset_name, charset_name
+ )
+ # Rename legacy encodings with superset encodings if asked
+ if self.should_rename_legacy:
+ charset_name = self.LEGACY_MAP.get(
+ (charset_name or "").lower(), charset_name
+ )
+ self.result = {
+ "encoding": charset_name,
+ "confidence": confidence,
+ "language": max_prober.language,
+ }
# Log all prober confidences if none met MINIMUM_THRESHOLD
if self.logger.getEffectiveLevel() <= logging.DEBUG:
- if self.result['encoding'] is None:
- self.logger.debug('no probers hit minimum threshold')
+ if self.result["encoding"] is None:
+ self.logger.debug("no probers hit minimum threshold")
for group_prober in self._charset_probers:
if not group_prober:
continue
if isinstance(group_prober, CharSetGroupProber):
for prober in group_prober.probers:
- self.logger.debug('%s %s confidence = %s',
- prober.charset_name,
- prober.language,
- prober.get_confidence())
+ self.logger.debug(
+ "%s %s confidence = %s",
+ prober.charset_name,
+ prober.language,
+ prober.get_confidence(),
+ )
else:
- self.logger.debug('%s %s confidence = %s',
- group_prober.charset_name,
- group_prober.language,
- group_prober.get_confidence())
+ self.logger.debug(
+ "%s %s confidence = %s",
+ group_prober.charset_name,
+ group_prober.language,
+ group_prober.get_confidence(),
+ )
return self.result
diff --git a/src/pip/_vendor/chardet/utf1632prober.py b/src/pip/_vendor/chardet/utf1632prober.py
new file mode 100644
index 000000000..6bdec63d6
--- /dev/null
+++ b/src/pip/_vendor/chardet/utf1632prober.py
@@ -0,0 +1,225 @@
+######################## BEGIN LICENSE BLOCK ########################
+#
+# Contributor(s):
+# Jason Zavaglia
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+# 02110-1301 USA
+######################### END LICENSE BLOCK #########################
+from typing import List, Union
+
+from .charsetprober import CharSetProber
+from .enums import ProbingState
+
+
+class UTF1632Prober(CharSetProber):
+ """
+ This class simply looks for occurrences of zero bytes, and infers
+ whether the file is UTF16 or UTF32 (low-endian or big-endian)
+ For instance, files looking like ( \0 \0 \0 [nonzero] )+
+ have a good probability to be UTF32BE. Files looking like ( \0 [nonzero] )+
+ may be guessed to be UTF16BE, and inversely for little-endian varieties.
+ """
+
+ # how many logical characters to scan before feeling confident of prediction
+ MIN_CHARS_FOR_DETECTION = 20
+ # a fixed constant ratio of expected zeros or non-zeros in modulo-position.
+ EXPECTED_RATIO = 0.94
+
+ def __init__(self) -> None:
+ super().__init__()
+ self.position = 0
+ self.zeros_at_mod = [0] * 4
+ self.nonzeros_at_mod = [0] * 4
+ self._state = ProbingState.DETECTING
+ self.quad = [0, 0, 0, 0]
+ self.invalid_utf16be = False
+ self.invalid_utf16le = False
+ self.invalid_utf32be = False
+ self.invalid_utf32le = False
+ self.first_half_surrogate_pair_detected_16be = False
+ self.first_half_surrogate_pair_detected_16le = False
+ self.reset()
+
+ def reset(self) -> None:
+ super().reset()
+ self.position = 0
+ self.zeros_at_mod = [0] * 4
+ self.nonzeros_at_mod = [0] * 4
+ self._state = ProbingState.DETECTING
+ self.invalid_utf16be = False
+ self.invalid_utf16le = False
+ self.invalid_utf32be = False
+ self.invalid_utf32le = False
+ self.first_half_surrogate_pair_detected_16be = False
+ self.first_half_surrogate_pair_detected_16le = False
+ self.quad = [0, 0, 0, 0]
+
+ @property
+ def charset_name(self) -> str:
+ if self.is_likely_utf32be():
+ return "utf-32be"
+ if self.is_likely_utf32le():
+ return "utf-32le"
+ if self.is_likely_utf16be():
+ return "utf-16be"
+ if self.is_likely_utf16le():
+ return "utf-16le"
+ # default to something valid
+ return "utf-16"
+
+ @property
+ def language(self) -> str:
+ return ""
+
+ def approx_32bit_chars(self) -> float:
+ return max(1.0, self.position / 4.0)
+
+ def approx_16bit_chars(self) -> float:
+ return max(1.0, self.position / 2.0)
+
+ def is_likely_utf32be(self) -> bool:
+ approx_chars = self.approx_32bit_chars()
+ return approx_chars >= self.MIN_CHARS_FOR_DETECTION and (
+ self.zeros_at_mod[0] / approx_chars > self.EXPECTED_RATIO
+ and self.zeros_at_mod[1] / approx_chars > self.EXPECTED_RATIO
+ and self.zeros_at_mod[2] / approx_chars > self.EXPECTED_RATIO
+ and self.nonzeros_at_mod[3] / approx_chars > self.EXPECTED_RATIO
+ and not self.invalid_utf32be
+ )
+
+ def is_likely_utf32le(self) -> bool:
+ approx_chars = self.approx_32bit_chars()
+ return approx_chars >= self.MIN_CHARS_FOR_DETECTION and (
+ self.nonzeros_at_mod[0] / approx_chars > self.EXPECTED_RATIO
+ and self.zeros_at_mod[1] / approx_chars > self.EXPECTED_RATIO
+ and self.zeros_at_mod[2] / approx_chars > self.EXPECTED_RATIO
+ and self.zeros_at_mod[3] / approx_chars > self.EXPECTED_RATIO
+ and not self.invalid_utf32le
+ )
+
+ def is_likely_utf16be(self) -> bool:
+ approx_chars = self.approx_16bit_chars()
+ return approx_chars >= self.MIN_CHARS_FOR_DETECTION and (
+ (self.nonzeros_at_mod[1] + self.nonzeros_at_mod[3]) / approx_chars
+ > self.EXPECTED_RATIO
+ and (self.zeros_at_mod[0] + self.zeros_at_mod[2]) / approx_chars
+ > self.EXPECTED_RATIO
+ and not self.invalid_utf16be
+ )
+
+ def is_likely_utf16le(self) -> bool:
+ approx_chars = self.approx_16bit_chars()
+ return approx_chars >= self.MIN_CHARS_FOR_DETECTION and (
+ (self.nonzeros_at_mod[0] + self.nonzeros_at_mod[2]) / approx_chars
+ > self.EXPECTED_RATIO
+ and (self.zeros_at_mod[1] + self.zeros_at_mod[3]) / approx_chars
+ > self.EXPECTED_RATIO
+ and not self.invalid_utf16le
+ )
+
+ def validate_utf32_characters(self, quad: List[int]) -> None:
+ """
+ Validate if the quad of bytes is valid UTF-32.
+
+ UTF-32 is valid in the range 0x00000000 - 0x0010FFFF
+ excluding 0x0000D800 - 0x0000DFFF
+
+ https://en.wikipedia.org/wiki/UTF-32
+ """
+ if (
+ quad[0] != 0
+ or quad[1] > 0x10
+ or (quad[0] == 0 and quad[1] == 0 and 0xD8 <= quad[2] <= 0xDF)
+ ):
+ self.invalid_utf32be = True
+ if (
+ quad[3] != 0
+ or quad[2] > 0x10
+ or (quad[3] == 0 and quad[2] == 0 and 0xD8 <= quad[1] <= 0xDF)
+ ):
+ self.invalid_utf32le = True
+
+ def validate_utf16_characters(self, pair: List[int]) -> None:
+ """
+ Validate if the pair of bytes is valid UTF-16.
+
+ UTF-16 is valid in the range 0x0000 - 0xFFFF excluding 0xD800 - 0xFFFF
+ with an exception for surrogate pairs, which must be in the range
+ 0xD800-0xDBFF followed by 0xDC00-0xDFFF
+
+ https://en.wikipedia.org/wiki/UTF-16
+ """
+ if not self.first_half_surrogate_pair_detected_16be:
+ if 0xD8 <= pair[0] <= 0xDB:
+ self.first_half_surrogate_pair_detected_16be = True
+ elif 0xDC <= pair[0] <= 0xDF:
+ self.invalid_utf16be = True
+ else:
+ if 0xDC <= pair[0] <= 0xDF:
+ self.first_half_surrogate_pair_detected_16be = False
+ else:
+ self.invalid_utf16be = True
+
+ if not self.first_half_surrogate_pair_detected_16le:
+ if 0xD8 <= pair[1] <= 0xDB:
+ self.first_half_surrogate_pair_detected_16le = True
+ elif 0xDC <= pair[1] <= 0xDF:
+ self.invalid_utf16le = True
+ else:
+ if 0xDC <= pair[1] <= 0xDF:
+ self.first_half_surrogate_pair_detected_16le = False
+ else:
+ self.invalid_utf16le = True
+
+ def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState:
+ for c in byte_str:
+ mod4 = self.position % 4
+ self.quad[mod4] = c
+ if mod4 == 3:
+ self.validate_utf32_characters(self.quad)
+ self.validate_utf16_characters(self.quad[0:2])
+ self.validate_utf16_characters(self.quad[2:4])
+ if c == 0:
+ self.zeros_at_mod[mod4] += 1
+ else:
+ self.nonzeros_at_mod[mod4] += 1
+ self.position += 1
+ return self.state
+
+ @property
+ def state(self) -> ProbingState:
+ if self._state in {ProbingState.NOT_ME, ProbingState.FOUND_IT}:
+ # terminal, decided states
+ return self._state
+ if self.get_confidence() > 0.80:
+ self._state = ProbingState.FOUND_IT
+ elif self.position > 4 * 1024:
+ # if we get to 4kb into the file, and we can't conclude it's UTF,
+ # let's give up
+ self._state = ProbingState.NOT_ME
+ return self._state
+
+ def get_confidence(self) -> float:
+ return (
+ 0.85
+ if (
+ self.is_likely_utf16le()
+ or self.is_likely_utf16be()
+ or self.is_likely_utf32le()
+ or self.is_likely_utf32be()
+ )
+ else 0.00
+ )
diff --git a/src/pip/_vendor/chardet/utf8prober.py b/src/pip/_vendor/chardet/utf8prober.py
index 6c3196cc2..d96354d97 100644
--- a/src/pip/_vendor/chardet/utf8prober.py
+++ b/src/pip/_vendor/chardet/utf8prober.py
@@ -25,45 +25,46 @@
# 02110-1301 USA
######################### END LICENSE BLOCK #########################
+from typing import Union
+
from .charsetprober import CharSetProber
-from .enums import ProbingState, MachineState
from .codingstatemachine import CodingStateMachine
+from .enums import MachineState, ProbingState
from .mbcssm import UTF8_SM_MODEL
-
class UTF8Prober(CharSetProber):
ONE_CHAR_PROB = 0.5
- def __init__(self):
- super(UTF8Prober, self).__init__()
+ def __init__(self) -> None:
+ super().__init__()
self.coding_sm = CodingStateMachine(UTF8_SM_MODEL)
- self._num_mb_chars = None
+ self._num_mb_chars = 0
self.reset()
- def reset(self):
- super(UTF8Prober, self).reset()
+ def reset(self) -> None:
+ super().reset()
self.coding_sm.reset()
self._num_mb_chars = 0
@property
- def charset_name(self):
+ def charset_name(self) -> str:
return "utf-8"
@property
- def language(self):
+ def language(self) -> str:
return ""
- def feed(self, byte_str):
+ def feed(self, byte_str: Union[bytes, bytearray]) -> ProbingState:
for c in byte_str:
coding_state = self.coding_sm.next_state(c)
if coding_state == MachineState.ERROR:
self._state = ProbingState.NOT_ME
break
- elif coding_state == MachineState.ITS_ME:
+ if coding_state == MachineState.ITS_ME:
self._state = ProbingState.FOUND_IT
break
- elif coding_state == MachineState.START:
+ if coding_state == MachineState.START:
if self.coding_sm.get_current_charlen() >= 2:
self._num_mb_chars += 1
@@ -73,10 +74,9 @@ class UTF8Prober(CharSetProber):
return self.state
- def get_confidence(self):
+ def get_confidence(self) -> float:
unlike = 0.99
if self._num_mb_chars < 6:
- unlike *= self.ONE_CHAR_PROB ** self._num_mb_chars
+ unlike *= self.ONE_CHAR_PROB**self._num_mb_chars
return 1.0 - unlike
- else:
- return unlike
+ return unlike
diff --git a/src/pip/_vendor/chardet/version.py b/src/pip/_vendor/chardet/version.py
index 70369b9d6..c5e9d85cd 100644
--- a/src/pip/_vendor/chardet/version.py
+++ b/src/pip/_vendor/chardet/version.py
@@ -1,9 +1,9 @@
"""
This module exists only to simplify retrieving the version number of chardet
-from within setup.py and from chardet subpackages.
+from within setuptools and from chardet subpackages.
:author: Dan Blanchard (dan.blanchard@gmail.com)
"""
-__version__ = "4.0.0"
-VERSION = __version__.split('.')
+__version__ = "5.1.0"
+VERSION = __version__.split(".")
diff --git a/src/pip/_vendor/colorama/__init__.py b/src/pip/_vendor/colorama/__init__.py
index b149ed79b..383101cdb 100644
--- a/src/pip/_vendor/colorama/__init__.py
+++ b/src/pip/_vendor/colorama/__init__.py
@@ -1,6 +1,7 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
-from .initialise import init, deinit, reinit, colorama_text
+from .initialise import init, deinit, reinit, colorama_text, just_fix_windows_console
from .ansi import Fore, Back, Style, Cursor
from .ansitowin32 import AnsiToWin32
-__version__ = '0.4.4'
+__version__ = '0.4.6'
+
diff --git a/src/pip/_vendor/colorama/ansitowin32.py b/src/pip/_vendor/colorama/ansitowin32.py
index 6039a0543..abf209e60 100644
--- a/src/pip/_vendor/colorama/ansitowin32.py
+++ b/src/pip/_vendor/colorama/ansitowin32.py
@@ -4,7 +4,7 @@ import sys
import os
from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style, BEL
-from .winterm import WinTerm, WinColor, WinStyle
+from .winterm import enable_vt_processing, WinTerm, WinColor, WinStyle
from .win32 import windll, winapi_test
@@ -37,6 +37,12 @@ class StreamWrapper(object):
def __exit__(self, *args, **kwargs):
return self.__wrapped.__exit__(*args, **kwargs)
+ def __setstate__(self, state):
+ self.__dict__ = state
+
+ def __getstate__(self):
+ return self.__dict__
+
def write(self, text):
self.__convertor.write(text)
@@ -57,7 +63,9 @@ class StreamWrapper(object):
stream = self.__wrapped
try:
return stream.closed
- except AttributeError:
+ # AttributeError in the case that the stream doesn't support being closed
+ # ValueError for the case that the stream has already been detached when atexit runs
+ except (AttributeError, ValueError):
return True
@@ -86,15 +94,22 @@ class AnsiToWin32(object):
# (e.g. Cygwin Terminal). In this case it's up to the terminal
# to support the ANSI codes.
conversion_supported = on_windows and winapi_test()
+ try:
+ fd = wrapped.fileno()
+ except Exception:
+ fd = -1
+ system_has_native_ansi = not on_windows or enable_vt_processing(fd)
+ have_tty = not self.stream.closed and self.stream.isatty()
+ need_conversion = conversion_supported and not system_has_native_ansi
# should we strip ANSI sequences from our output?
if strip is None:
- strip = conversion_supported or (not self.stream.closed and not self.stream.isatty())
+ strip = need_conversion or not have_tty
self.strip = strip
# should we should convert ANSI sequences into win32 calls?
if convert is None:
- convert = conversion_supported and not self.stream.closed and self.stream.isatty()
+ convert = need_conversion and have_tty
self.convert = convert
# dict of ansi codes to win32 functions and parameters
@@ -256,3 +271,7 @@ class AnsiToWin32(object):
if params[0] in '02':
winterm.set_title(params[1])
return text
+
+
+ def flush(self):
+ self.wrapped.flush()
diff --git a/src/pip/_vendor/colorama/initialise.py b/src/pip/_vendor/colorama/initialise.py
index 430d06687..d5fd4b71f 100644
--- a/src/pip/_vendor/colorama/initialise.py
+++ b/src/pip/_vendor/colorama/initialise.py
@@ -6,13 +6,27 @@ import sys
from .ansitowin32 import AnsiToWin32
-orig_stdout = None
-orig_stderr = None
+def _wipe_internal_state_for_tests():
+ global orig_stdout, orig_stderr
+ orig_stdout = None
+ orig_stderr = None
+
+ global wrapped_stdout, wrapped_stderr
+ wrapped_stdout = None
+ wrapped_stderr = None
-wrapped_stdout = None
-wrapped_stderr = None
+ global atexit_done
+ atexit_done = False
+
+ global fixed_windows_console
+ fixed_windows_console = False
-atexit_done = False
+ try:
+ # no-op if it wasn't registered
+ atexit.unregister(reset_all)
+ except AttributeError:
+ # python 2: no atexit.unregister. Oh well, we did our best.
+ pass
def reset_all():
@@ -55,6 +69,29 @@ def deinit():
sys.stderr = orig_stderr
+def just_fix_windows_console():
+ global fixed_windows_console
+
+ if sys.platform != "win32":
+ return
+ if fixed_windows_console:
+ return
+ if wrapped_stdout is not None or wrapped_stderr is not None:
+ # Someone already ran init() and it did stuff, so we won't second-guess them
+ return
+
+ # On newer versions of Windows, AnsiToWin32.__init__ will implicitly enable the
+ # native ANSI support in the console as a side-effect. We only need to actually
+ # replace sys.stdout/stderr if we're in the old-style conversion mode.
+ new_stdout = AnsiToWin32(sys.stdout, convert=None, strip=None, autoreset=False)
+ if new_stdout.convert:
+ sys.stdout = new_stdout
+ new_stderr = AnsiToWin32(sys.stderr, convert=None, strip=None, autoreset=False)
+ if new_stderr.convert:
+ sys.stderr = new_stderr
+
+ fixed_windows_console = True
+
@contextlib.contextmanager
def colorama_text(*args, **kwargs):
init(*args, **kwargs)
@@ -78,3 +115,7 @@ def wrap_stream(stream, convert, strip, autoreset, wrap):
if wrapper.should_wrap():
stream = wrapper.stream
return stream
+
+
+# Use this for initial setup as well, to reduce code duplication
+_wipe_internal_state_for_tests()
diff --git a/src/pip/_vendor/colorama/tests/__init__.py b/src/pip/_vendor/colorama/tests/__init__.py
new file mode 100644
index 000000000..8c5661e93
--- /dev/null
+++ b/src/pip/_vendor/colorama/tests/__init__.py
@@ -0,0 +1 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
diff --git a/src/pip/_vendor/colorama/tests/ansi_test.py b/src/pip/_vendor/colorama/tests/ansi_test.py
new file mode 100644
index 000000000..0a20c80f8
--- /dev/null
+++ b/src/pip/_vendor/colorama/tests/ansi_test.py
@@ -0,0 +1,76 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+import sys
+from unittest import TestCase, main
+
+from ..ansi import Back, Fore, Style
+from ..ansitowin32 import AnsiToWin32
+
+stdout_orig = sys.stdout
+stderr_orig = sys.stderr
+
+
+class AnsiTest(TestCase):
+
+ def setUp(self):
+ # sanity check: stdout should be a file or StringIO object.
+ # It will only be AnsiToWin32 if init() has previously wrapped it
+ self.assertNotEqual(type(sys.stdout), AnsiToWin32)
+ self.assertNotEqual(type(sys.stderr), AnsiToWin32)
+
+ def tearDown(self):
+ sys.stdout = stdout_orig
+ sys.stderr = stderr_orig
+
+
+ def testForeAttributes(self):
+ self.assertEqual(Fore.BLACK, '\033[30m')
+ self.assertEqual(Fore.RED, '\033[31m')
+ self.assertEqual(Fore.GREEN, '\033[32m')
+ self.assertEqual(Fore.YELLOW, '\033[33m')
+ self.assertEqual(Fore.BLUE, '\033[34m')
+ self.assertEqual(Fore.MAGENTA, '\033[35m')
+ self.assertEqual(Fore.CYAN, '\033[36m')
+ self.assertEqual(Fore.WHITE, '\033[37m')
+ self.assertEqual(Fore.RESET, '\033[39m')
+
+ # Check the light, extended versions.
+ self.assertEqual(Fore.LIGHTBLACK_EX, '\033[90m')
+ self.assertEqual(Fore.LIGHTRED_EX, '\033[91m')
+ self.assertEqual(Fore.LIGHTGREEN_EX, '\033[92m')
+ self.assertEqual(Fore.LIGHTYELLOW_EX, '\033[93m')
+ self.assertEqual(Fore.LIGHTBLUE_EX, '\033[94m')
+ self.assertEqual(Fore.LIGHTMAGENTA_EX, '\033[95m')
+ self.assertEqual(Fore.LIGHTCYAN_EX, '\033[96m')
+ self.assertEqual(Fore.LIGHTWHITE_EX, '\033[97m')
+
+
+ def testBackAttributes(self):
+ self.assertEqual(Back.BLACK, '\033[40m')
+ self.assertEqual(Back.RED, '\033[41m')
+ self.assertEqual(Back.GREEN, '\033[42m')
+ self.assertEqual(Back.YELLOW, '\033[43m')
+ self.assertEqual(Back.BLUE, '\033[44m')
+ self.assertEqual(Back.MAGENTA, '\033[45m')
+ self.assertEqual(Back.CYAN, '\033[46m')
+ self.assertEqual(Back.WHITE, '\033[47m')
+ self.assertEqual(Back.RESET, '\033[49m')
+
+ # Check the light, extended versions.
+ self.assertEqual(Back.LIGHTBLACK_EX, '\033[100m')
+ self.assertEqual(Back.LIGHTRED_EX, '\033[101m')
+ self.assertEqual(Back.LIGHTGREEN_EX, '\033[102m')
+ self.assertEqual(Back.LIGHTYELLOW_EX, '\033[103m')
+ self.assertEqual(Back.LIGHTBLUE_EX, '\033[104m')
+ self.assertEqual(Back.LIGHTMAGENTA_EX, '\033[105m')
+ self.assertEqual(Back.LIGHTCYAN_EX, '\033[106m')
+ self.assertEqual(Back.LIGHTWHITE_EX, '\033[107m')
+
+
+ def testStyleAttributes(self):
+ self.assertEqual(Style.DIM, '\033[2m')
+ self.assertEqual(Style.NORMAL, '\033[22m')
+ self.assertEqual(Style.BRIGHT, '\033[1m')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/src/pip/_vendor/colorama/tests/ansitowin32_test.py b/src/pip/_vendor/colorama/tests/ansitowin32_test.py
new file mode 100644
index 000000000..91ca551f9
--- /dev/null
+++ b/src/pip/_vendor/colorama/tests/ansitowin32_test.py
@@ -0,0 +1,294 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+from io import StringIO, TextIOWrapper
+from unittest import TestCase, main
+try:
+ from contextlib import ExitStack
+except ImportError:
+ # python 2
+ from contextlib2 import ExitStack
+
+try:
+ from unittest.mock import MagicMock, Mock, patch
+except ImportError:
+ from mock import MagicMock, Mock, patch
+
+from ..ansitowin32 import AnsiToWin32, StreamWrapper
+from ..win32 import ENABLE_VIRTUAL_TERMINAL_PROCESSING
+from .utils import osname
+
+
+class StreamWrapperTest(TestCase):
+
+ def testIsAProxy(self):
+ mockStream = Mock()
+ wrapper = StreamWrapper(mockStream, None)
+ self.assertTrue( wrapper.random_attr is mockStream.random_attr )
+
+ def testDelegatesWrite(self):
+ mockStream = Mock()
+ mockConverter = Mock()
+ wrapper = StreamWrapper(mockStream, mockConverter)
+ wrapper.write('hello')
+ self.assertTrue(mockConverter.write.call_args, (('hello',), {}))
+
+ def testDelegatesContext(self):
+ mockConverter = Mock()
+ s = StringIO()
+ with StreamWrapper(s, mockConverter) as fp:
+ fp.write(u'hello')
+ self.assertTrue(s.closed)
+
+ def testProxyNoContextManager(self):
+ mockStream = MagicMock()
+ mockStream.__enter__.side_effect = AttributeError()
+ mockConverter = Mock()
+ with self.assertRaises(AttributeError) as excinfo:
+ with StreamWrapper(mockStream, mockConverter) as wrapper:
+ wrapper.write('hello')
+
+ def test_closed_shouldnt_raise_on_closed_stream(self):
+ stream = StringIO()
+ stream.close()
+ wrapper = StreamWrapper(stream, None)
+ self.assertEqual(wrapper.closed, True)
+
+ def test_closed_shouldnt_raise_on_detached_stream(self):
+ stream = TextIOWrapper(StringIO())
+ stream.detach()
+ wrapper = StreamWrapper(stream, None)
+ self.assertEqual(wrapper.closed, True)
+
+class AnsiToWin32Test(TestCase):
+
+ def testInit(self):
+ mockStdout = Mock()
+ auto = Mock()
+ stream = AnsiToWin32(mockStdout, autoreset=auto)
+ self.assertEqual(stream.wrapped, mockStdout)
+ self.assertEqual(stream.autoreset, auto)
+
+ @patch('colorama.ansitowin32.winterm', None)
+ @patch('colorama.ansitowin32.winapi_test', lambda *_: True)
+ def testStripIsTrueOnWindows(self):
+ with osname('nt'):
+ mockStdout = Mock()
+ stream = AnsiToWin32(mockStdout)
+ self.assertTrue(stream.strip)
+
+ def testStripIsFalseOffWindows(self):
+ with osname('posix'):
+ mockStdout = Mock(closed=False)
+ stream = AnsiToWin32(mockStdout)
+ self.assertFalse(stream.strip)
+
+ def testWriteStripsAnsi(self):
+ mockStdout = Mock()
+ stream = AnsiToWin32(mockStdout)
+ stream.wrapped = Mock()
+ stream.write_and_convert = Mock()
+ stream.strip = True
+
+ stream.write('abc')
+
+ self.assertFalse(stream.wrapped.write.called)
+ self.assertEqual(stream.write_and_convert.call_args, (('abc',), {}))
+
+ def testWriteDoesNotStripAnsi(self):
+ mockStdout = Mock()
+ stream = AnsiToWin32(mockStdout)
+ stream.wrapped = Mock()
+ stream.write_and_convert = Mock()
+ stream.strip = False
+ stream.convert = False
+
+ stream.write('abc')
+
+ self.assertFalse(stream.write_and_convert.called)
+ self.assertEqual(stream.wrapped.write.call_args, (('abc',), {}))
+
+ def assert_autoresets(self, convert, autoreset=True):
+ stream = AnsiToWin32(Mock())
+ stream.convert = convert
+ stream.reset_all = Mock()
+ stream.autoreset = autoreset
+ stream.winterm = Mock()
+
+ stream.write('abc')
+
+ self.assertEqual(stream.reset_all.called, autoreset)
+
+ def testWriteAutoresets(self):
+ self.assert_autoresets(convert=True)
+ self.assert_autoresets(convert=False)
+ self.assert_autoresets(convert=True, autoreset=False)
+ self.assert_autoresets(convert=False, autoreset=False)
+
+ def testWriteAndConvertWritesPlainText(self):
+ stream = AnsiToWin32(Mock())
+ stream.write_and_convert( 'abc' )
+ self.assertEqual( stream.wrapped.write.call_args, (('abc',), {}) )
+
+ def testWriteAndConvertStripsAllValidAnsi(self):
+ stream = AnsiToWin32(Mock())
+ stream.call_win32 = Mock()
+ data = [
+ 'abc\033[mdef',
+ 'abc\033[0mdef',
+ 'abc\033[2mdef',
+ 'abc\033[02mdef',
+ 'abc\033[002mdef',
+ 'abc\033[40mdef',
+ 'abc\033[040mdef',
+ 'abc\033[0;1mdef',
+ 'abc\033[40;50mdef',
+ 'abc\033[50;30;40mdef',
+ 'abc\033[Adef',
+ 'abc\033[0Gdef',
+ 'abc\033[1;20;128Hdef',
+ ]
+ for datum in data:
+ stream.wrapped.write.reset_mock()
+ stream.write_and_convert( datum )
+ self.assertEqual(
+ [args[0] for args in stream.wrapped.write.call_args_list],
+ [ ('abc',), ('def',) ]
+ )
+
+ def testWriteAndConvertSkipsEmptySnippets(self):
+ stream = AnsiToWin32(Mock())
+ stream.call_win32 = Mock()
+ stream.write_and_convert( '\033[40m\033[41m' )
+ self.assertFalse( stream.wrapped.write.called )
+
+ def testWriteAndConvertCallsWin32WithParamsAndCommand(self):
+ stream = AnsiToWin32(Mock())
+ stream.convert = True
+ stream.call_win32 = Mock()
+ stream.extract_params = Mock(return_value='params')
+ data = {
+ 'abc\033[adef': ('a', 'params'),
+ 'abc\033[;;bdef': ('b', 'params'),
+ 'abc\033[0cdef': ('c', 'params'),
+ 'abc\033[;;0;;Gdef': ('G', 'params'),
+ 'abc\033[1;20;128Hdef': ('H', 'params'),
+ }
+ for datum, expected in data.items():
+ stream.call_win32.reset_mock()
+ stream.write_and_convert( datum )
+ self.assertEqual( stream.call_win32.call_args[0], expected )
+
+ def test_reset_all_shouldnt_raise_on_closed_orig_stdout(self):
+ stream = StringIO()
+ converter = AnsiToWin32(stream)
+ stream.close()
+
+ converter.reset_all()
+
+ def test_wrap_shouldnt_raise_on_closed_orig_stdout(self):
+ stream = StringIO()
+ stream.close()
+ with \
+ patch("colorama.ansitowin32.os.name", "nt"), \
+ patch("colorama.ansitowin32.winapi_test", lambda: True):
+ converter = AnsiToWin32(stream)
+ self.assertTrue(converter.strip)
+ self.assertFalse(converter.convert)
+
+ def test_wrap_shouldnt_raise_on_missing_closed_attr(self):
+ with \
+ patch("colorama.ansitowin32.os.name", "nt"), \
+ patch("colorama.ansitowin32.winapi_test", lambda: True):
+ converter = AnsiToWin32(object())
+ self.assertTrue(converter.strip)
+ self.assertFalse(converter.convert)
+
+ def testExtractParams(self):
+ stream = AnsiToWin32(Mock())
+ data = {
+ '': (0,),
+ ';;': (0,),
+ '2': (2,),
+ ';;002;;': (2,),
+ '0;1': (0, 1),
+ ';;003;;456;;': (3, 456),
+ '11;22;33;44;55': (11, 22, 33, 44, 55),
+ }
+ for datum, expected in data.items():
+ self.assertEqual(stream.extract_params('m', datum), expected)
+
+ def testCallWin32UsesLookup(self):
+ listener = Mock()
+ stream = AnsiToWin32(listener)
+ stream.win32_calls = {
+ 1: (lambda *_, **__: listener(11),),
+ 2: (lambda *_, **__: listener(22),),
+ 3: (lambda *_, **__: listener(33),),
+ }
+ stream.call_win32('m', (3, 1, 99, 2))
+ self.assertEqual(
+ [a[0][0] for a in listener.call_args_list],
+ [33, 11, 22] )
+
+ def test_osc_codes(self):
+ mockStdout = Mock()
+ stream = AnsiToWin32(mockStdout, convert=True)
+ with patch('colorama.ansitowin32.winterm') as winterm:
+ data = [
+ '\033]0\x07', # missing arguments
+ '\033]0;foo\x08', # wrong OSC command
+ '\033]0;colorama_test_title\x07', # should work
+ '\033]1;colorama_test_title\x07', # wrong set command
+ '\033]2;colorama_test_title\x07', # should work
+ '\033]' + ';' * 64 + '\x08', # see issue #247
+ ]
+ for code in data:
+ stream.write(code)
+ self.assertEqual(winterm.set_title.call_count, 2)
+
+ def test_native_windows_ansi(self):
+ with ExitStack() as stack:
+ def p(a, b):
+ stack.enter_context(patch(a, b, create=True))
+ # Pretend to be on Windows
+ p("colorama.ansitowin32.os.name", "nt")
+ p("colorama.ansitowin32.winapi_test", lambda: True)
+ p("colorama.win32.winapi_test", lambda: True)
+ p("colorama.winterm.win32.windll", "non-None")
+ p("colorama.winterm.get_osfhandle", lambda _: 1234)
+
+ # Pretend that our mock stream has native ANSI support
+ p(
+ "colorama.winterm.win32.GetConsoleMode",
+ lambda _: ENABLE_VIRTUAL_TERMINAL_PROCESSING,
+ )
+ SetConsoleMode = Mock()
+ p("colorama.winterm.win32.SetConsoleMode", SetConsoleMode)
+
+ stdout = Mock()
+ stdout.closed = False
+ stdout.isatty.return_value = True
+ stdout.fileno.return_value = 1
+
+ # Our fake console says it has native vt support, so AnsiToWin32 should
+ # enable that support and do nothing else.
+ stream = AnsiToWin32(stdout)
+ SetConsoleMode.assert_called_with(1234, ENABLE_VIRTUAL_TERMINAL_PROCESSING)
+ self.assertFalse(stream.strip)
+ self.assertFalse(stream.convert)
+ self.assertFalse(stream.should_wrap())
+
+ # Now let's pretend we're on an old Windows console, that doesn't have
+ # native ANSI support.
+ p("colorama.winterm.win32.GetConsoleMode", lambda _: 0)
+ SetConsoleMode = Mock()
+ p("colorama.winterm.win32.SetConsoleMode", SetConsoleMode)
+
+ stream = AnsiToWin32(stdout)
+ SetConsoleMode.assert_called_with(1234, ENABLE_VIRTUAL_TERMINAL_PROCESSING)
+ self.assertTrue(stream.strip)
+ self.assertTrue(stream.convert)
+ self.assertTrue(stream.should_wrap())
+
+
+if __name__ == '__main__':
+ main()
diff --git a/src/pip/_vendor/colorama/tests/initialise_test.py b/src/pip/_vendor/colorama/tests/initialise_test.py
new file mode 100644
index 000000000..89f9b0751
--- /dev/null
+++ b/src/pip/_vendor/colorama/tests/initialise_test.py
@@ -0,0 +1,189 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+import sys
+from unittest import TestCase, main, skipUnless
+
+try:
+ from unittest.mock import patch, Mock
+except ImportError:
+ from mock import patch, Mock
+
+from ..ansitowin32 import StreamWrapper
+from ..initialise import init, just_fix_windows_console, _wipe_internal_state_for_tests
+from .utils import osname, replace_by
+
+orig_stdout = sys.stdout
+orig_stderr = sys.stderr
+
+
+class InitTest(TestCase):
+
+ @skipUnless(sys.stdout.isatty(), "sys.stdout is not a tty")
+ def setUp(self):
+ # sanity check
+ self.assertNotWrapped()
+
+ def tearDown(self):
+ _wipe_internal_state_for_tests()
+ sys.stdout = orig_stdout
+ sys.stderr = orig_stderr
+
+ def assertWrapped(self):
+ self.assertIsNot(sys.stdout, orig_stdout, 'stdout should be wrapped')
+ self.assertIsNot(sys.stderr, orig_stderr, 'stderr should be wrapped')
+ self.assertTrue(isinstance(sys.stdout, StreamWrapper),
+ 'bad stdout wrapper')
+ self.assertTrue(isinstance(sys.stderr, StreamWrapper),
+ 'bad stderr wrapper')
+
+ def assertNotWrapped(self):
+ self.assertIs(sys.stdout, orig_stdout, 'stdout should not be wrapped')
+ self.assertIs(sys.stderr, orig_stderr, 'stderr should not be wrapped')
+
+ @patch('colorama.initialise.reset_all')
+ @patch('colorama.ansitowin32.winapi_test', lambda *_: True)
+ @patch('colorama.ansitowin32.enable_vt_processing', lambda *_: False)
+ def testInitWrapsOnWindows(self, _):
+ with osname("nt"):
+ init()
+ self.assertWrapped()
+
+ @patch('colorama.initialise.reset_all')
+ @patch('colorama.ansitowin32.winapi_test', lambda *_: False)
+ def testInitDoesntWrapOnEmulatedWindows(self, _):
+ with osname("nt"):
+ init()
+ self.assertNotWrapped()
+
+ def testInitDoesntWrapOnNonWindows(self):
+ with osname("posix"):
+ init()
+ self.assertNotWrapped()
+
+ def testInitDoesntWrapIfNone(self):
+ with replace_by(None):
+ init()
+ # We can't use assertNotWrapped here because replace_by(None)
+ # changes stdout/stderr already.
+ self.assertIsNone(sys.stdout)
+ self.assertIsNone(sys.stderr)
+
+ def testInitAutoresetOnWrapsOnAllPlatforms(self):
+ with osname("posix"):
+ init(autoreset=True)
+ self.assertWrapped()
+
+ def testInitWrapOffDoesntWrapOnWindows(self):
+ with osname("nt"):
+ init(wrap=False)
+ self.assertNotWrapped()
+
+ def testInitWrapOffIncompatibleWithAutoresetOn(self):
+ self.assertRaises(ValueError, lambda: init(autoreset=True, wrap=False))
+
+ @patch('colorama.win32.SetConsoleTextAttribute')
+ @patch('colorama.initialise.AnsiToWin32')
+ def testAutoResetPassedOn(self, mockATW32, _):
+ with osname("nt"):
+ init(autoreset=True)
+ self.assertEqual(len(mockATW32.call_args_list), 2)
+ self.assertEqual(mockATW32.call_args_list[1][1]['autoreset'], True)
+ self.assertEqual(mockATW32.call_args_list[0][1]['autoreset'], True)
+
+ @patch('colorama.initialise.AnsiToWin32')
+ def testAutoResetChangeable(self, mockATW32):
+ with osname("nt"):
+ init()
+
+ init(autoreset=True)
+ self.assertEqual(len(mockATW32.call_args_list), 4)
+ self.assertEqual(mockATW32.call_args_list[2][1]['autoreset'], True)
+ self.assertEqual(mockATW32.call_args_list[3][1]['autoreset'], True)
+
+ init()
+ self.assertEqual(len(mockATW32.call_args_list), 6)
+ self.assertEqual(
+ mockATW32.call_args_list[4][1]['autoreset'], False)
+ self.assertEqual(
+ mockATW32.call_args_list[5][1]['autoreset'], False)
+
+
+ @patch('colorama.initialise.atexit.register')
+ def testAtexitRegisteredOnlyOnce(self, mockRegister):
+ init()
+ self.assertTrue(mockRegister.called)
+ mockRegister.reset_mock()
+ init()
+ self.assertFalse(mockRegister.called)
+
+
+class JustFixWindowsConsoleTest(TestCase):
+ def _reset(self):
+ _wipe_internal_state_for_tests()
+ sys.stdout = orig_stdout
+ sys.stderr = orig_stderr
+
+ def tearDown(self):
+ self._reset()
+
+ @patch("colorama.ansitowin32.winapi_test", lambda: True)
+ def testJustFixWindowsConsole(self):
+ if sys.platform != "win32":
+ # just_fix_windows_console should be a no-op
+ just_fix_windows_console()
+ self.assertIs(sys.stdout, orig_stdout)
+ self.assertIs(sys.stderr, orig_stderr)
+ else:
+ def fake_std():
+ # Emulate stdout=not a tty, stderr=tty
+ # to check that we handle both cases correctly
+ stdout = Mock()
+ stdout.closed = False
+ stdout.isatty.return_value = False
+ stdout.fileno.return_value = 1
+ sys.stdout = stdout
+
+ stderr = Mock()
+ stderr.closed = False
+ stderr.isatty.return_value = True
+ stderr.fileno.return_value = 2
+ sys.stderr = stderr
+
+ for native_ansi in [False, True]:
+ with patch(
+ 'colorama.ansitowin32.enable_vt_processing',
+ lambda *_: native_ansi
+ ):
+ self._reset()
+ fake_std()
+
+ # Regular single-call test
+ prev_stdout = sys.stdout
+ prev_stderr = sys.stderr
+ just_fix_windows_console()
+ self.assertIs(sys.stdout, prev_stdout)
+ if native_ansi:
+ self.assertIs(sys.stderr, prev_stderr)
+ else:
+ self.assertIsNot(sys.stderr, prev_stderr)
+
+ # second call without resetting is always a no-op
+ prev_stdout = sys.stdout
+ prev_stderr = sys.stderr
+ just_fix_windows_console()
+ self.assertIs(sys.stdout, prev_stdout)
+ self.assertIs(sys.stderr, prev_stderr)
+
+ self._reset()
+ fake_std()
+
+ # If init() runs first, just_fix_windows_console should be a no-op
+ init()
+ prev_stdout = sys.stdout
+ prev_stderr = sys.stderr
+ just_fix_windows_console()
+ self.assertIs(prev_stdout, sys.stdout)
+ self.assertIs(prev_stderr, sys.stderr)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/src/pip/_vendor/colorama/tests/isatty_test.py b/src/pip/_vendor/colorama/tests/isatty_test.py
new file mode 100644
index 000000000..0f84e4bef
--- /dev/null
+++ b/src/pip/_vendor/colorama/tests/isatty_test.py
@@ -0,0 +1,57 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+import sys
+from unittest import TestCase, main
+
+from ..ansitowin32 import StreamWrapper, AnsiToWin32
+from .utils import pycharm, replace_by, replace_original_by, StreamTTY, StreamNonTTY
+
+
+def is_a_tty(stream):
+ return StreamWrapper(stream, None).isatty()
+
+class IsattyTest(TestCase):
+
+ def test_TTY(self):
+ tty = StreamTTY()
+ self.assertTrue(is_a_tty(tty))
+ with pycharm():
+ self.assertTrue(is_a_tty(tty))
+
+ def test_nonTTY(self):
+ non_tty = StreamNonTTY()
+ self.assertFalse(is_a_tty(non_tty))
+ with pycharm():
+ self.assertFalse(is_a_tty(non_tty))
+
+ def test_withPycharm(self):
+ with pycharm():
+ self.assertTrue(is_a_tty(sys.stderr))
+ self.assertTrue(is_a_tty(sys.stdout))
+
+ def test_withPycharmTTYOverride(self):
+ tty = StreamTTY()
+ with pycharm(), replace_by(tty):
+ self.assertTrue(is_a_tty(tty))
+
+ def test_withPycharmNonTTYOverride(self):
+ non_tty = StreamNonTTY()
+ with pycharm(), replace_by(non_tty):
+ self.assertFalse(is_a_tty(non_tty))
+
+ def test_withPycharmNoneOverride(self):
+ with pycharm():
+ with replace_by(None), replace_original_by(None):
+ self.assertFalse(is_a_tty(None))
+ self.assertFalse(is_a_tty(StreamNonTTY()))
+ self.assertTrue(is_a_tty(StreamTTY()))
+
+ def test_withPycharmStreamWrapped(self):
+ with pycharm():
+ self.assertTrue(AnsiToWin32(StreamTTY()).stream.isatty())
+ self.assertFalse(AnsiToWin32(StreamNonTTY()).stream.isatty())
+ self.assertTrue(AnsiToWin32(sys.stdout).stream.isatty())
+ self.assertTrue(AnsiToWin32(sys.stderr).stream.isatty())
+
+
+if __name__ == '__main__':
+ main()
diff --git a/src/pip/_vendor/colorama/tests/utils.py b/src/pip/_vendor/colorama/tests/utils.py
new file mode 100644
index 000000000..472fafb44
--- /dev/null
+++ b/src/pip/_vendor/colorama/tests/utils.py
@@ -0,0 +1,49 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+from contextlib import contextmanager
+from io import StringIO
+import sys
+import os
+
+
+class StreamTTY(StringIO):
+ def isatty(self):
+ return True
+
+class StreamNonTTY(StringIO):
+ def isatty(self):
+ return False
+
+@contextmanager
+def osname(name):
+ orig = os.name
+ os.name = name
+ yield
+ os.name = orig
+
+@contextmanager
+def replace_by(stream):
+ orig_stdout = sys.stdout
+ orig_stderr = sys.stderr
+ sys.stdout = stream
+ sys.stderr = stream
+ yield
+ sys.stdout = orig_stdout
+ sys.stderr = orig_stderr
+
+@contextmanager
+def replace_original_by(stream):
+ orig_stdout = sys.__stdout__
+ orig_stderr = sys.__stderr__
+ sys.__stdout__ = stream
+ sys.__stderr__ = stream
+ yield
+ sys.__stdout__ = orig_stdout
+ sys.__stderr__ = orig_stderr
+
+@contextmanager
+def pycharm():
+ os.environ["PYCHARM_HOSTED"] = "1"
+ non_tty = StreamNonTTY()
+ with replace_by(non_tty), replace_original_by(non_tty):
+ yield
+ del os.environ["PYCHARM_HOSTED"]
diff --git a/src/pip/_vendor/colorama/tests/winterm_test.py b/src/pip/_vendor/colorama/tests/winterm_test.py
new file mode 100644
index 000000000..d0955f9e6
--- /dev/null
+++ b/src/pip/_vendor/colorama/tests/winterm_test.py
@@ -0,0 +1,131 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+import sys
+from unittest import TestCase, main, skipUnless
+
+try:
+ from unittest.mock import Mock, patch
+except ImportError:
+ from mock import Mock, patch
+
+from ..winterm import WinColor, WinStyle, WinTerm
+
+
+class WinTermTest(TestCase):
+
+ @patch('colorama.winterm.win32')
+ def testInit(self, mockWin32):
+ mockAttr = Mock()
+ mockAttr.wAttributes = 7 + 6 * 16 + 8
+ mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr
+ term = WinTerm()
+ self.assertEqual(term._fore, 7)
+ self.assertEqual(term._back, 6)
+ self.assertEqual(term._style, 8)
+
+ @skipUnless(sys.platform.startswith("win"), "requires Windows")
+ def testGetAttrs(self):
+ term = WinTerm()
+
+ term._fore = 0
+ term._back = 0
+ term._style = 0
+ self.assertEqual(term.get_attrs(), 0)
+
+ term._fore = WinColor.YELLOW
+ self.assertEqual(term.get_attrs(), WinColor.YELLOW)
+
+ term._back = WinColor.MAGENTA
+ self.assertEqual(
+ term.get_attrs(),
+ WinColor.YELLOW + WinColor.MAGENTA * 16)
+
+ term._style = WinStyle.BRIGHT
+ self.assertEqual(
+ term.get_attrs(),
+ WinColor.YELLOW + WinColor.MAGENTA * 16 + WinStyle.BRIGHT)
+
+ @patch('colorama.winterm.win32')
+ def testResetAll(self, mockWin32):
+ mockAttr = Mock()
+ mockAttr.wAttributes = 1 + 2 * 16 + 8
+ mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr
+ term = WinTerm()
+
+ term.set_console = Mock()
+ term._fore = -1
+ term._back = -1
+ term._style = -1
+
+ term.reset_all()
+
+ self.assertEqual(term._fore, 1)
+ self.assertEqual(term._back, 2)
+ self.assertEqual(term._style, 8)
+ self.assertEqual(term.set_console.called, True)
+
+ @skipUnless(sys.platform.startswith("win"), "requires Windows")
+ def testFore(self):
+ term = WinTerm()
+ term.set_console = Mock()
+ term._fore = 0
+
+ term.fore(5)
+
+ self.assertEqual(term._fore, 5)
+ self.assertEqual(term.set_console.called, True)
+
+ @skipUnless(sys.platform.startswith("win"), "requires Windows")
+ def testBack(self):
+ term = WinTerm()
+ term.set_console = Mock()
+ term._back = 0
+
+ term.back(5)
+
+ self.assertEqual(term._back, 5)
+ self.assertEqual(term.set_console.called, True)
+
+ @skipUnless(sys.platform.startswith("win"), "requires Windows")
+ def testStyle(self):
+ term = WinTerm()
+ term.set_console = Mock()
+ term._style = 0
+
+ term.style(22)
+
+ self.assertEqual(term._style, 22)
+ self.assertEqual(term.set_console.called, True)
+
+ @patch('colorama.winterm.win32')
+ def testSetConsole(self, mockWin32):
+ mockAttr = Mock()
+ mockAttr.wAttributes = 0
+ mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr
+ term = WinTerm()
+ term.windll = Mock()
+
+ term.set_console()
+
+ self.assertEqual(
+ mockWin32.SetConsoleTextAttribute.call_args,
+ ((mockWin32.STDOUT, term.get_attrs()), {})
+ )
+
+ @patch('colorama.winterm.win32')
+ def testSetConsoleOnStderr(self, mockWin32):
+ mockAttr = Mock()
+ mockAttr.wAttributes = 0
+ mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr
+ term = WinTerm()
+ term.windll = Mock()
+
+ term.set_console(on_stderr=True)
+
+ self.assertEqual(
+ mockWin32.SetConsoleTextAttribute.call_args,
+ ((mockWin32.STDERR, term.get_attrs()), {})
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/src/pip/_vendor/colorama/win32.py b/src/pip/_vendor/colorama/win32.py
index c2d836033..841b0e270 100644
--- a/src/pip/_vendor/colorama/win32.py
+++ b/src/pip/_vendor/colorama/win32.py
@@ -4,6 +4,8 @@
STDOUT = -11
STDERR = -12
+ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
+
try:
import ctypes
from ctypes import LibraryLoader
@@ -89,6 +91,20 @@ else:
]
_SetConsoleTitleW.restype = wintypes.BOOL
+ _GetConsoleMode = windll.kernel32.GetConsoleMode
+ _GetConsoleMode.argtypes = [
+ wintypes.HANDLE,
+ POINTER(wintypes.DWORD)
+ ]
+ _GetConsoleMode.restype = wintypes.BOOL
+
+ _SetConsoleMode = windll.kernel32.SetConsoleMode
+ _SetConsoleMode.argtypes = [
+ wintypes.HANDLE,
+ wintypes.DWORD
+ ]
+ _SetConsoleMode.restype = wintypes.BOOL
+
def _winapi_test(handle):
csbi = CONSOLE_SCREEN_BUFFER_INFO()
success = _GetConsoleScreenBufferInfo(
@@ -150,3 +166,15 @@ else:
def SetConsoleTitle(title):
return _SetConsoleTitleW(title)
+
+ def GetConsoleMode(handle):
+ mode = wintypes.DWORD()
+ success = _GetConsoleMode(handle, byref(mode))
+ if not success:
+ raise ctypes.WinError()
+ return mode.value
+
+ def SetConsoleMode(handle, mode):
+ success = _SetConsoleMode(handle, mode)
+ if not success:
+ raise ctypes.WinError()
diff --git a/src/pip/_vendor/colorama/winterm.py b/src/pip/_vendor/colorama/winterm.py
index 0fdb4ec4e..aad867e8c 100644
--- a/src/pip/_vendor/colorama/winterm.py
+++ b/src/pip/_vendor/colorama/winterm.py
@@ -1,6 +1,12 @@
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
-from . import win32
+try:
+ from msvcrt import get_osfhandle
+except ImportError:
+ def get_osfhandle(_):
+ raise OSError("This isn't windows!")
+
+from . import win32
# from wincon.h
class WinColor(object):
@@ -167,3 +173,23 @@ class WinTerm(object):
def set_title(self, title):
win32.SetConsoleTitle(title)
+
+
+def enable_vt_processing(fd):
+ if win32.windll is None or not win32.winapi_test():
+ return False
+
+ try:
+ handle = get_osfhandle(fd)
+ mode = win32.GetConsoleMode(handle)
+ win32.SetConsoleMode(
+ handle,
+ mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING,
+ )
+
+ mode = win32.GetConsoleMode(handle)
+ if mode & win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING:
+ return True
+ # Can get TypeError in testsuite where 'fd' is a Mock()
+ except (OSError, TypeError):
+ return False
diff --git a/src/pip/_vendor/distlib/__init__.py b/src/pip/_vendor/distlib/__init__.py
index 115494810..962173c8d 100644
--- a/src/pip/_vendor/distlib/__init__.py
+++ b/src/pip/_vendor/distlib/__init__.py
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2012-2019 Vinay Sajip.
+# Copyright (C) 2012-2022 Vinay Sajip.
# Licensed to the Python Software Foundation under a contributor agreement.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
import logging
-__version__ = '0.3.3'
+__version__ = '0.3.6'
class DistlibException(Exception):
pass
diff --git a/src/pip/_vendor/distlib/_backport/__init__.py b/src/pip/_vendor/distlib/_backport/__init__.py
deleted file mode 100644
index f7dbf4c9a..000000000
--- a/src/pip/_vendor/distlib/_backport/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""Modules copied from Python 3 standard libraries, for internal use only.
-
-Individual classes and functions are found in d2._backport.misc. Intended
-usage is to always import things missing from 3.1 from that module: the
-built-in/stdlib objects will be used if found.
-"""
diff --git a/src/pip/_vendor/distlib/_backport/misc.py b/src/pip/_vendor/distlib/_backport/misc.py
deleted file mode 100644
index cfb318d34..000000000
--- a/src/pip/_vendor/distlib/_backport/misc.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2012 The Python Software Foundation.
-# See LICENSE.txt and CONTRIBUTORS.txt.
-#
-"""Backports for individual classes and functions."""
-
-import os
-import sys
-
-__all__ = ['cache_from_source', 'callable', 'fsencode']
-
-
-try:
- from imp import cache_from_source
-except ImportError:
- def cache_from_source(py_file, debug=__debug__):
- ext = debug and 'c' or 'o'
- return py_file + ext
-
-
-try:
- callable = callable
-except NameError:
- from collections import Callable
-
- def callable(obj):
- return isinstance(obj, Callable)
-
-
-try:
- fsencode = os.fsencode
-except AttributeError:
- def fsencode(filename):
- if isinstance(filename, bytes):
- return filename
- elif isinstance(filename, str):
- return filename.encode(sys.getfilesystemencoding())
- else:
- raise TypeError("expect bytes or str, not %s" %
- type(filename).__name__)
diff --git a/src/pip/_vendor/distlib/_backport/shutil.py b/src/pip/_vendor/distlib/_backport/shutil.py
deleted file mode 100644
index 10ed36253..000000000
--- a/src/pip/_vendor/distlib/_backport/shutil.py
+++ /dev/null
@@ -1,764 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2012 The Python Software Foundation.
-# See LICENSE.txt and CONTRIBUTORS.txt.
-#
-"""Utility functions for copying and archiving files and directory trees.
-
-XXX The functions here don't copy the resource fork or other metadata on Mac.
-
-"""
-
-import os
-import sys
-import stat
-from os.path import abspath
-import fnmatch
-try:
- from collections.abc import Callable
-except ImportError:
- from collections import Callable
-import errno
-from . import tarfile
-
-try:
- import bz2
- _BZ2_SUPPORTED = True
-except ImportError:
- _BZ2_SUPPORTED = False
-
-try:
- from pwd import getpwnam
-except ImportError:
- getpwnam = None
-
-try:
- from grp import getgrnam
-except ImportError:
- getgrnam = None
-
-__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
- "copytree", "move", "rmtree", "Error", "SpecialFileError",
- "ExecError", "make_archive", "get_archive_formats",
- "register_archive_format", "unregister_archive_format",
- "get_unpack_formats", "register_unpack_format",
- "unregister_unpack_format", "unpack_archive", "ignore_patterns"]
-
-class Error(EnvironmentError):
- pass
-
-class SpecialFileError(EnvironmentError):
- """Raised when trying to do a kind of operation (e.g. copying) which is
- not supported on a special file (e.g. a named pipe)"""
-
-class ExecError(EnvironmentError):
- """Raised when a command could not be executed"""
-
-class ReadError(EnvironmentError):
- """Raised when an archive cannot be read"""
-
-class RegistryError(Exception):
- """Raised when a registry operation with the archiving
- and unpacking registries fails"""
-
-
-try:
- WindowsError
-except NameError:
- WindowsError = None
-
-def copyfileobj(fsrc, fdst, length=16*1024):
- """copy data from file-like object fsrc to file-like object fdst"""
- while 1:
- buf = fsrc.read(length)
- if not buf:
- break
- fdst.write(buf)
-
-def _samefile(src, dst):
- # Macintosh, Unix.
- if hasattr(os.path, 'samefile'):
- try:
- return os.path.samefile(src, dst)
- except OSError:
- return False
-
- # All other platforms: check for same pathname.
- return (os.path.normcase(os.path.abspath(src)) ==
- os.path.normcase(os.path.abspath(dst)))
-
-def copyfile(src, dst):
- """Copy data from src to dst"""
- if _samefile(src, dst):
- raise Error("`%s` and `%s` are the same file" % (src, dst))
-
- for fn in [src, dst]:
- try:
- st = os.stat(fn)
- except OSError:
- # File most likely does not exist
- pass
- else:
- # XXX What about other special files? (sockets, devices...)
- if stat.S_ISFIFO(st.st_mode):
- raise SpecialFileError("`%s` is a named pipe" % fn)
-
- with open(src, 'rb') as fsrc:
- with open(dst, 'wb') as fdst:
- copyfileobj(fsrc, fdst)
-
-def copymode(src, dst):
- """Copy mode bits from src to dst"""
- if hasattr(os, 'chmod'):
- st = os.stat(src)
- mode = stat.S_IMODE(st.st_mode)
- os.chmod(dst, mode)
-
-def copystat(src, dst):
- """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
- st = os.stat(src)
- mode = stat.S_IMODE(st.st_mode)
- if hasattr(os, 'utime'):
- os.utime(dst, (st.st_atime, st.st_mtime))
- if hasattr(os, 'chmod'):
- os.chmod(dst, mode)
- if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
- try:
- os.chflags(dst, st.st_flags)
- except OSError as why:
- if (not hasattr(errno, 'EOPNOTSUPP') or
- why.errno != errno.EOPNOTSUPP):
- raise
-
-def copy(src, dst):
- """Copy data and mode bits ("cp src dst").
-
- The destination may be a directory.
-
- """
- if os.path.isdir(dst):
- dst = os.path.join(dst, os.path.basename(src))
- copyfile(src, dst)
- copymode(src, dst)
-
-def copy2(src, dst):
- """Copy data and all stat info ("cp -p src dst").
-
- The destination may be a directory.
-
- """
- if os.path.isdir(dst):
- dst = os.path.join(dst, os.path.basename(src))
- copyfile(src, dst)
- copystat(src, dst)
-
-def ignore_patterns(*patterns):
- """Function that can be used as copytree() ignore parameter.
-
- Patterns is a sequence of glob-style patterns
- that are used to exclude files"""
- def _ignore_patterns(path, names):
- ignored_names = []
- for pattern in patterns:
- ignored_names.extend(fnmatch.filter(names, pattern))
- return set(ignored_names)
- return _ignore_patterns
-
-def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
- ignore_dangling_symlinks=False):
- """Recursively copy a directory tree.
-
- The destination directory must not already exist.
- If exception(s) occur, an Error is raised with a list of reasons.
-
- If the optional symlinks flag is true, symbolic links in the
- source tree result in symbolic links in the destination tree; if
- it is false, the contents of the files pointed to by symbolic
- links are copied. If the file pointed by the symlink doesn't
- exist, an exception will be added in the list of errors raised in
- an Error exception at the end of the copy process.
-
- You can set the optional ignore_dangling_symlinks flag to true if you
- want to silence this exception. Notice that this has no effect on
- platforms that don't support os.symlink.
-
- The optional ignore argument is a callable. If given, it
- is called with the `src` parameter, which is the directory
- being visited by copytree(), and `names` which is the list of
- `src` contents, as returned by os.listdir():
-
- callable(src, names) -> ignored_names
-
- Since copytree() is called recursively, the callable will be
- called once for each directory that is copied. It returns a
- list of names relative to the `src` directory that should
- not be copied.
-
- The optional copy_function argument is a callable that will be used
- to copy each file. It will be called with the source path and the
- destination path as arguments. By default, copy2() is used, but any
- function that supports the same signature (like copy()) can be used.
-
- """
- names = os.listdir(src)
- if ignore is not None:
- ignored_names = ignore(src, names)
- else:
- ignored_names = set()
-
- os.makedirs(dst)
- errors = []
- for name in names:
- if name in ignored_names:
- continue
- srcname = os.path.join(src, name)
- dstname = os.path.join(dst, name)
- try:
- if os.path.islink(srcname):
- linkto = os.readlink(srcname)
- if symlinks:
- os.symlink(linkto, dstname)
- else:
- # ignore dangling symlink if the flag is on
- if not os.path.exists(linkto) and ignore_dangling_symlinks:
- continue
- # otherwise let the copy occurs. copy2 will raise an error
- copy_function(srcname, dstname)
- elif os.path.isdir(srcname):
- copytree(srcname, dstname, symlinks, ignore, copy_function)
- else:
- # Will raise a SpecialFileError for unsupported file types
- copy_function(srcname, dstname)
- # catch the Error from the recursive copytree so that we can
- # continue with other files
- except Error as err:
- errors.extend(err.args[0])
- except EnvironmentError as why:
- errors.append((srcname, dstname, str(why)))
- try:
- copystat(src, dst)
- except OSError as why:
- if WindowsError is not None and isinstance(why, WindowsError):
- # Copying file access times may fail on Windows
- pass
- else:
- errors.extend((src, dst, str(why)))
- if errors:
- raise Error(errors)
-
-def rmtree(path, ignore_errors=False, onerror=None):
- """Recursively delete a directory tree.
-
- If ignore_errors is set, errors are ignored; otherwise, if onerror
- is set, it is called to handle the error with arguments (func,
- path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
- path is the argument to that function that caused it to fail; and
- exc_info is a tuple returned by sys.exc_info(). If ignore_errors
- is false and onerror is None, an exception is raised.
-
- """
- if ignore_errors:
- def onerror(*args):
- pass
- elif onerror is None:
- def onerror(*args):
- raise
- try:
- if os.path.islink(path):
- # symlinks to directories are forbidden, see bug #1669
- raise OSError("Cannot call rmtree on a symbolic link")
- except OSError:
- onerror(os.path.islink, path, sys.exc_info())
- # can't continue even if onerror hook returns
- return
- names = []
- try:
- names = os.listdir(path)
- except os.error:
- onerror(os.listdir, path, sys.exc_info())
- for name in names:
- fullname = os.path.join(path, name)
- try:
- mode = os.lstat(fullname).st_mode
- except os.error:
- mode = 0
- if stat.S_ISDIR(mode):
- rmtree(fullname, ignore_errors, onerror)
- else:
- try:
- os.remove(fullname)
- except os.error:
- onerror(os.remove, fullname, sys.exc_info())
- try:
- os.rmdir(path)
- except os.error:
- onerror(os.rmdir, path, sys.exc_info())
-
-
-def _basename(path):
- # A basename() variant which first strips the trailing slash, if present.
- # Thus we always get the last component of the path, even for directories.
- return os.path.basename(path.rstrip(os.path.sep))
-
-def move(src, dst):
- """Recursively move a file or directory to another location. This is
- similar to the Unix "mv" command.
-
- If the destination is a directory or a symlink to a directory, the source
- is moved inside the directory. The destination path must not already
- exist.
-
- If the destination already exists but is not a directory, it may be
- overwritten depending on os.rename() semantics.
-
- If the destination is on our current filesystem, then rename() is used.
- Otherwise, src is copied to the destination and then removed.
- A lot more could be done here... A look at a mv.c shows a lot of
- the issues this implementation glosses over.
-
- """
- real_dst = dst
- if os.path.isdir(dst):
- if _samefile(src, dst):
- # We might be on a case insensitive filesystem,
- # perform the rename anyway.
- os.rename(src, dst)
- return
-
- real_dst = os.path.join(dst, _basename(src))
- if os.path.exists(real_dst):
- raise Error("Destination path '%s' already exists" % real_dst)
- try:
- os.rename(src, real_dst)
- except OSError:
- if os.path.isdir(src):
- if _destinsrc(src, dst):
- raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
- copytree(src, real_dst, symlinks=True)
- rmtree(src)
- else:
- copy2(src, real_dst)
- os.unlink(src)
-
-def _destinsrc(src, dst):
- src = abspath(src)
- dst = abspath(dst)
- if not src.endswith(os.path.sep):
- src += os.path.sep
- if not dst.endswith(os.path.sep):
- dst += os.path.sep
- return dst.startswith(src)
-
-def _get_gid(name):
- """Returns a gid, given a group name."""
- if getgrnam is None or name is None:
- return None
- try:
- result = getgrnam(name)
- except KeyError:
- result = None
- if result is not None:
- return result[2]
- return None
-
-def _get_uid(name):
- """Returns an uid, given a user name."""
- if getpwnam is None or name is None:
- return None
- try:
- result = getpwnam(name)
- except KeyError:
- result = None
- if result is not None:
- return result[2]
- return None
-
-def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
- owner=None, group=None, logger=None):
- """Create a (possibly compressed) tar file from all the files under
- 'base_dir'.
-
- 'compress' must be "gzip" (the default), "bzip2", or None.
-
- 'owner' and 'group' can be used to define an owner and a group for the
- archive that is being built. If not provided, the current owner and group
- will be used.
-
- The output tar file will be named 'base_name' + ".tar", possibly plus
- the appropriate compression extension (".gz", or ".bz2").
-
- Returns the output filename.
- """
- tar_compression = {'gzip': 'gz', None: ''}
- compress_ext = {'gzip': '.gz'}
-
- if _BZ2_SUPPORTED:
- tar_compression['bzip2'] = 'bz2'
- compress_ext['bzip2'] = '.bz2'
-
- # flags for compression program, each element of list will be an argument
- if compress is not None and compress not in compress_ext:
- raise ValueError("bad value for 'compress', or compression format not "
- "supported : {0}".format(compress))
-
- archive_name = base_name + '.tar' + compress_ext.get(compress, '')
- archive_dir = os.path.dirname(archive_name)
-
- if not os.path.exists(archive_dir):
- if logger is not None:
- logger.info("creating %s", archive_dir)
- if not dry_run:
- os.makedirs(archive_dir)
-
- # creating the tarball
- if logger is not None:
- logger.info('Creating tar archive')
-
- uid = _get_uid(owner)
- gid = _get_gid(group)
-
- def _set_uid_gid(tarinfo):
- if gid is not None:
- tarinfo.gid = gid
- tarinfo.gname = group
- if uid is not None:
- tarinfo.uid = uid
- tarinfo.uname = owner
- return tarinfo
-
- if not dry_run:
- tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
- try:
- tar.add(base_dir, filter=_set_uid_gid)
- finally:
- tar.close()
-
- return archive_name
-
-def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False):
- # XXX see if we want to keep an external call here
- if verbose:
- zipoptions = "-r"
- else:
- zipoptions = "-rq"
- from distutils.errors import DistutilsExecError
- from distutils.spawn import spawn
- try:
- spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
- except DistutilsExecError:
- # XXX really should distinguish between "couldn't find
- # external 'zip' command" and "zip failed".
- raise ExecError("unable to create zip file '%s': "
- "could neither import the 'zipfile' module nor "
- "find a standalone zip utility") % zip_filename
-
-def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
- """Create a zip file from all the files under 'base_dir'.
-
- The output zip file will be named 'base_name' + ".zip". Uses either the
- "zipfile" Python module (if available) or the InfoZIP "zip" utility
- (if installed and found on the default search path). If neither tool is
- available, raises ExecError. Returns the name of the output zip
- file.
- """
- zip_filename = base_name + ".zip"
- archive_dir = os.path.dirname(base_name)
-
- if not os.path.exists(archive_dir):
- if logger is not None:
- logger.info("creating %s", archive_dir)
- if not dry_run:
- os.makedirs(archive_dir)
-
- # If zipfile module is not available, try spawning an external 'zip'
- # command.
- try:
- import zipfile
- except ImportError:
- zipfile = None
-
- if zipfile is None:
- _call_external_zip(base_dir, zip_filename, verbose, dry_run)
- else:
- if logger is not None:
- logger.info("creating '%s' and adding '%s' to it",
- zip_filename, base_dir)
-
- if not dry_run:
- zip = zipfile.ZipFile(zip_filename, "w",
- compression=zipfile.ZIP_DEFLATED)
-
- for dirpath, dirnames, filenames in os.walk(base_dir):
- for name in filenames:
- path = os.path.normpath(os.path.join(dirpath, name))
- if os.path.isfile(path):
- zip.write(path, path)
- if logger is not None:
- logger.info("adding '%s'", path)
- zip.close()
-
- return zip_filename
-
-_ARCHIVE_FORMATS = {
- 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
- 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
- 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
- 'zip': (_make_zipfile, [], "ZIP file"),
- }
-
-if _BZ2_SUPPORTED:
- _ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')],
- "bzip2'ed tar-file")
-
-def get_archive_formats():
- """Returns a list of supported formats for archiving and unarchiving.
-
- Each element of the returned sequence is a tuple (name, description)
- """
- formats = [(name, registry[2]) for name, registry in
- _ARCHIVE_FORMATS.items()]
- formats.sort()
- return formats
-
-def register_archive_format(name, function, extra_args=None, description=''):
- """Registers an archive format.
-
- name is the name of the format. function is the callable that will be
- used to create archives. If provided, extra_args is a sequence of
- (name, value) tuples that will be passed as arguments to the callable.
- description can be provided to describe the format, and will be returned
- by the get_archive_formats() function.
- """
- if extra_args is None:
- extra_args = []
- if not isinstance(function, Callable):
- raise TypeError('The %s object is not callable' % function)
- if not isinstance(extra_args, (tuple, list)):
- raise TypeError('extra_args needs to be a sequence')
- for element in extra_args:
- if not isinstance(element, (tuple, list)) or len(element) !=2:
- raise TypeError('extra_args elements are : (arg_name, value)')
-
- _ARCHIVE_FORMATS[name] = (function, extra_args, description)
-
-def unregister_archive_format(name):
- del _ARCHIVE_FORMATS[name]
-
-def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
- dry_run=0, owner=None, group=None, logger=None):
- """Create an archive file (eg. zip or tar).
-
- 'base_name' is the name of the file to create, minus any format-specific
- extension; 'format' is the archive format: one of "zip", "tar", "bztar"
- or "gztar".
-
- 'root_dir' is a directory that will be the root directory of the
- archive; ie. we typically chdir into 'root_dir' before creating the
- archive. 'base_dir' is the directory where we start archiving from;
- ie. 'base_dir' will be the common prefix of all files and
- directories in the archive. 'root_dir' and 'base_dir' both default
- to the current directory. Returns the name of the archive file.
-
- 'owner' and 'group' are used when creating a tar archive. By default,
- uses the current owner and group.
- """
- save_cwd = os.getcwd()
- if root_dir is not None:
- if logger is not None:
- logger.debug("changing into '%s'", root_dir)
- base_name = os.path.abspath(base_name)
- if not dry_run:
- os.chdir(root_dir)
-
- if base_dir is None:
- base_dir = os.curdir
-
- kwargs = {'dry_run': dry_run, 'logger': logger}
-
- try:
- format_info = _ARCHIVE_FORMATS[format]
- except KeyError:
- raise ValueError("unknown archive format '%s'" % format)
-
- func = format_info[0]
- for arg, val in format_info[1]:
- kwargs[arg] = val
-
- if format != 'zip':
- kwargs['owner'] = owner
- kwargs['group'] = group
-
- try:
- filename = func(base_name, base_dir, **kwargs)
- finally:
- if root_dir is not None:
- if logger is not None:
- logger.debug("changing back to '%s'", save_cwd)
- os.chdir(save_cwd)
-
- return filename
-
-
-def get_unpack_formats():
- """Returns a list of supported formats for unpacking.
-
- Each element of the returned sequence is a tuple
- (name, extensions, description)
- """
- formats = [(name, info[0], info[3]) for name, info in
- _UNPACK_FORMATS.items()]
- formats.sort()
- return formats
-
-def _check_unpack_options(extensions, function, extra_args):
- """Checks what gets registered as an unpacker."""
- # first make sure no other unpacker is registered for this extension
- existing_extensions = {}
- for name, info in _UNPACK_FORMATS.items():
- for ext in info[0]:
- existing_extensions[ext] = name
-
- for extension in extensions:
- if extension in existing_extensions:
- msg = '%s is already registered for "%s"'
- raise RegistryError(msg % (extension,
- existing_extensions[extension]))
-
- if not isinstance(function, Callable):
- raise TypeError('The registered function must be a callable')
-
-
-def register_unpack_format(name, extensions, function, extra_args=None,
- description=''):
- """Registers an unpack format.
-
- `name` is the name of the format. `extensions` is a list of extensions
- corresponding to the format.
-
- `function` is the callable that will be
- used to unpack archives. The callable will receive archives to unpack.
- If it's unable to handle an archive, it needs to raise a ReadError
- exception.
-
- If provided, `extra_args` is a sequence of
- (name, value) tuples that will be passed as arguments to the callable.
- description can be provided to describe the format, and will be returned
- by the get_unpack_formats() function.
- """
- if extra_args is None:
- extra_args = []
- _check_unpack_options(extensions, function, extra_args)
- _UNPACK_FORMATS[name] = extensions, function, extra_args, description
-
-def unregister_unpack_format(name):
- """Removes the pack format from the registry."""
- del _UNPACK_FORMATS[name]
-
-def _ensure_directory(path):
- """Ensure that the parent directory of `path` exists"""
- dirname = os.path.dirname(path)
- if not os.path.isdir(dirname):
- os.makedirs(dirname)
-
-def _unpack_zipfile(filename, extract_dir):
- """Unpack zip `filename` to `extract_dir`
- """
- try:
- import zipfile
- except ImportError:
- raise ReadError('zlib not supported, cannot unpack this archive.')
-
- if not zipfile.is_zipfile(filename):
- raise ReadError("%s is not a zip file" % filename)
-
- zip = zipfile.ZipFile(filename)
- try:
- for info in zip.infolist():
- name = info.filename
-
- # don't extract absolute paths or ones with .. in them
- if name.startswith('/') or '..' in name:
- continue
-
- target = os.path.join(extract_dir, *name.split('/'))
- if not target:
- continue
-
- _ensure_directory(target)
- if not name.endswith('/'):
- # file
- data = zip.read(info.filename)
- f = open(target, 'wb')
- try:
- f.write(data)
- finally:
- f.close()
- del data
- finally:
- zip.close()
-
-def _unpack_tarfile(filename, extract_dir):
- """Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir`
- """
- try:
- tarobj = tarfile.open(filename)
- except tarfile.TarError:
- raise ReadError(
- "%s is not a compressed or uncompressed tar file" % filename)
- try:
- tarobj.extractall(extract_dir)
- finally:
- tarobj.close()
-
-_UNPACK_FORMATS = {
- 'gztar': (['.tar.gz', '.tgz'], _unpack_tarfile, [], "gzip'ed tar-file"),
- 'tar': (['.tar'], _unpack_tarfile, [], "uncompressed tar file"),
- 'zip': (['.zip'], _unpack_zipfile, [], "ZIP file")
- }
-
-if _BZ2_SUPPORTED:
- _UNPACK_FORMATS['bztar'] = (['.bz2'], _unpack_tarfile, [],
- "bzip2'ed tar-file")
-
-def _find_unpack_format(filename):
- for name, info in _UNPACK_FORMATS.items():
- for extension in info[0]:
- if filename.endswith(extension):
- return name
- return None
-
-def unpack_archive(filename, extract_dir=None, format=None):
- """Unpack an archive.
-
- `filename` is the name of the archive.
-
- `extract_dir` is the name of the target directory, where the archive
- is unpacked. If not provided, the current working directory is used.
-
- `format` is the archive format: one of "zip", "tar", or "gztar". Or any
- other registered format. If not provided, unpack_archive will use the
- filename extension and see if an unpacker was registered for that
- extension.
-
- In case none is found, a ValueError is raised.
- """
- if extract_dir is None:
- extract_dir = os.getcwd()
-
- if format is not None:
- try:
- format_info = _UNPACK_FORMATS[format]
- except KeyError:
- raise ValueError("Unknown unpack format '{0}'".format(format))
-
- func = format_info[1]
- func(filename, extract_dir, **dict(format_info[2]))
- else:
- # we need to look at the registered unpackers supported extensions
- format = _find_unpack_format(filename)
- if format is None:
- raise ReadError("Unknown archive format '{0}'".format(filename))
-
- func = _UNPACK_FORMATS[format][1]
- kwargs = dict(_UNPACK_FORMATS[format][2])
- func(filename, extract_dir, **kwargs)
diff --git a/src/pip/_vendor/distlib/_backport/sysconfig.cfg b/src/pip/_vendor/distlib/_backport/sysconfig.cfg
deleted file mode 100644
index 1746bd01c..000000000
--- a/src/pip/_vendor/distlib/_backport/sysconfig.cfg
+++ /dev/null
@@ -1,84 +0,0 @@
-[posix_prefix]
-# Configuration directories. Some of these come straight out of the
-# configure script. They are for implementing the other variables, not to
-# be used directly in [resource_locations].
-confdir = /etc
-datadir = /usr/share
-libdir = /usr/lib
-statedir = /var
-# User resource directory
-local = ~/.local/{distribution.name}
-
-stdlib = {base}/lib/python{py_version_short}
-platstdlib = {platbase}/lib/python{py_version_short}
-purelib = {base}/lib/python{py_version_short}/site-packages
-platlib = {platbase}/lib/python{py_version_short}/site-packages
-include = {base}/include/python{py_version_short}{abiflags}
-platinclude = {platbase}/include/python{py_version_short}{abiflags}
-data = {base}
-
-[posix_home]
-stdlib = {base}/lib/python
-platstdlib = {base}/lib/python
-purelib = {base}/lib/python
-platlib = {base}/lib/python
-include = {base}/include/python
-platinclude = {base}/include/python
-scripts = {base}/bin
-data = {base}
-
-[nt]
-stdlib = {base}/Lib
-platstdlib = {base}/Lib
-purelib = {base}/Lib/site-packages
-platlib = {base}/Lib/site-packages
-include = {base}/Include
-platinclude = {base}/Include
-scripts = {base}/Scripts
-data = {base}
-
-[os2]
-stdlib = {base}/Lib
-platstdlib = {base}/Lib
-purelib = {base}/Lib/site-packages
-platlib = {base}/Lib/site-packages
-include = {base}/Include
-platinclude = {base}/Include
-scripts = {base}/Scripts
-data = {base}
-
-[os2_home]
-stdlib = {userbase}/lib/python{py_version_short}
-platstdlib = {userbase}/lib/python{py_version_short}
-purelib = {userbase}/lib/python{py_version_short}/site-packages
-platlib = {userbase}/lib/python{py_version_short}/site-packages
-include = {userbase}/include/python{py_version_short}
-scripts = {userbase}/bin
-data = {userbase}
-
-[nt_user]
-stdlib = {userbase}/Python{py_version_nodot}
-platstdlib = {userbase}/Python{py_version_nodot}
-purelib = {userbase}/Python{py_version_nodot}/site-packages
-platlib = {userbase}/Python{py_version_nodot}/site-packages
-include = {userbase}/Python{py_version_nodot}/Include
-scripts = {userbase}/Scripts
-data = {userbase}
-
-[posix_user]
-stdlib = {userbase}/lib/python{py_version_short}
-platstdlib = {userbase}/lib/python{py_version_short}
-purelib = {userbase}/lib/python{py_version_short}/site-packages
-platlib = {userbase}/lib/python{py_version_short}/site-packages
-include = {userbase}/include/python{py_version_short}
-scripts = {userbase}/bin
-data = {userbase}
-
-[osx_framework_user]
-stdlib = {userbase}/lib/python
-platstdlib = {userbase}/lib/python
-purelib = {userbase}/lib/python/site-packages
-platlib = {userbase}/lib/python/site-packages
-include = {userbase}/include
-scripts = {userbase}/bin
-data = {userbase}
diff --git a/src/pip/_vendor/distlib/_backport/sysconfig.py b/src/pip/_vendor/distlib/_backport/sysconfig.py
deleted file mode 100644
index b470a373c..000000000
--- a/src/pip/_vendor/distlib/_backport/sysconfig.py
+++ /dev/null
@@ -1,786 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2012 The Python Software Foundation.
-# See LICENSE.txt and CONTRIBUTORS.txt.
-#
-"""Access to Python's configuration information."""
-
-import codecs
-import os
-import re
-import sys
-from os.path import pardir, realpath
-try:
- import configparser
-except ImportError:
- import ConfigParser as configparser
-
-
-__all__ = [
- 'get_config_h_filename',
- 'get_config_var',
- 'get_config_vars',
- 'get_makefile_filename',
- 'get_path',
- 'get_path_names',
- 'get_paths',
- 'get_platform',
- 'get_python_version',
- 'get_scheme_names',
- 'parse_config_h',
-]
-
-
-def _safe_realpath(path):
- try:
- return realpath(path)
- except OSError:
- return path
-
-
-if sys.executable:
- _PROJECT_BASE = os.path.dirname(_safe_realpath(sys.executable))
-else:
- # sys.executable can be empty if argv[0] has been changed and Python is
- # unable to retrieve the real program name
- _PROJECT_BASE = _safe_realpath(os.getcwd())
-
-if os.name == "nt" and "pcbuild" in _PROJECT_BASE[-8:].lower():
- _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir))
-# PC/VS7.1
-if os.name == "nt" and "\\pc\\v" in _PROJECT_BASE[-10:].lower():
- _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
-# PC/AMD64
-if os.name == "nt" and "\\pcbuild\\amd64" in _PROJECT_BASE[-14:].lower():
- _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
-
-
-def is_python_build():
- for fn in ("Setup.dist", "Setup.local"):
- if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)):
- return True
- return False
-
-_PYTHON_BUILD = is_python_build()
-
-_cfg_read = False
-
-def _ensure_cfg_read():
- global _cfg_read
- if not _cfg_read:
- from ..resources import finder
- backport_package = __name__.rsplit('.', 1)[0]
- _finder = finder(backport_package)
- _cfgfile = _finder.find('sysconfig.cfg')
- assert _cfgfile, 'sysconfig.cfg exists'
- with _cfgfile.as_stream() as s:
- _SCHEMES.readfp(s)
- if _PYTHON_BUILD:
- for scheme in ('posix_prefix', 'posix_home'):
- _SCHEMES.set(scheme, 'include', '{srcdir}/Include')
- _SCHEMES.set(scheme, 'platinclude', '{projectbase}/.')
-
- _cfg_read = True
-
-
-_SCHEMES = configparser.RawConfigParser()
-_VAR_REPL = re.compile(r'\{([^{]*?)\}')
-
-def _expand_globals(config):
- _ensure_cfg_read()
- if config.has_section('globals'):
- globals = config.items('globals')
- else:
- globals = tuple()
-
- sections = config.sections()
- for section in sections:
- if section == 'globals':
- continue
- for option, value in globals:
- if config.has_option(section, option):
- continue
- config.set(section, option, value)
- config.remove_section('globals')
-
- # now expanding local variables defined in the cfg file
- #
- for section in config.sections():
- variables = dict(config.items(section))
-
- def _replacer(matchobj):
- name = matchobj.group(1)
- if name in variables:
- return variables[name]
- return matchobj.group(0)
-
- for option, value in config.items(section):
- config.set(section, option, _VAR_REPL.sub(_replacer, value))
-
-#_expand_globals(_SCHEMES)
-
-_PY_VERSION = '%s.%s.%s' % sys.version_info[:3]
-_PY_VERSION_SHORT = '%s.%s' % sys.version_info[:2]
-_PY_VERSION_SHORT_NO_DOT = '%s%s' % sys.version_info[:2]
-_PREFIX = os.path.normpath(sys.prefix)
-_EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
-_CONFIG_VARS = None
-_USER_BASE = None
-
-
-def _subst_vars(path, local_vars):
- """In the string `path`, replace tokens like {some.thing} with the
- corresponding value from the map `local_vars`.
-
- If there is no corresponding value, leave the token unchanged.
- """
- def _replacer(matchobj):
- name = matchobj.group(1)
- if name in local_vars:
- return local_vars[name]
- elif name in os.environ:
- return os.environ[name]
- return matchobj.group(0)
- return _VAR_REPL.sub(_replacer, path)
-
-
-def _extend_dict(target_dict, other_dict):
- target_keys = target_dict.keys()
- for key, value in other_dict.items():
- if key in target_keys:
- continue
- target_dict[key] = value
-
-
-def _expand_vars(scheme, vars):
- res = {}
- if vars is None:
- vars = {}
- _extend_dict(vars, get_config_vars())
-
- for key, value in _SCHEMES.items(scheme):
- if os.name in ('posix', 'nt'):
- value = os.path.expanduser(value)
- res[key] = os.path.normpath(_subst_vars(value, vars))
- return res
-
-
-def format_value(value, vars):
- def _replacer(matchobj):
- name = matchobj.group(1)
- if name in vars:
- return vars[name]
- return matchobj.group(0)
- return _VAR_REPL.sub(_replacer, value)
-
-
-def _get_default_scheme():
- if os.name == 'posix':
- # the default scheme for posix is posix_prefix
- return 'posix_prefix'
- return os.name
-
-
-def _getuserbase():
- env_base = os.environ.get("PYTHONUSERBASE", None)
-
- def joinuser(*args):
- return os.path.expanduser(os.path.join(*args))
-
- # what about 'os2emx', 'riscos' ?
- if os.name == "nt":
- base = os.environ.get("APPDATA") or "~"
- if env_base:
- return env_base
- else:
- return joinuser(base, "Python")
-
- if sys.platform == "darwin":
- framework = get_config_var("PYTHONFRAMEWORK")
- if framework:
- if env_base:
- return env_base
- else:
- return joinuser("~", "Library", framework, "%d.%d" %
- sys.version_info[:2])
-
- if env_base:
- return env_base
- else:
- return joinuser("~", ".local")
-
-
-def _parse_makefile(filename, vars=None):
- """Parse a Makefile-style file.
-
- A dictionary containing name/value pairs is returned. If an
- optional dictionary is passed in as the second argument, it is
- used instead of a new dictionary.
- """
- # Regexes needed for parsing Makefile (and similar syntaxes,
- # like old-style Setup files).
- _variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)")
- _findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")
- _findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")
-
- if vars is None:
- vars = {}
- done = {}
- notdone = {}
-
- with codecs.open(filename, encoding='utf-8', errors="surrogateescape") as f:
- lines = f.readlines()
-
- for line in lines:
- if line.startswith('#') or line.strip() == '':
- continue
- m = _variable_rx.match(line)
- if m:
- n, v = m.group(1, 2)
- v = v.strip()
- # `$$' is a literal `$' in make
- tmpv = v.replace('$$', '')
-
- if "$" in tmpv:
- notdone[n] = v
- else:
- try:
- v = int(v)
- except ValueError:
- # insert literal `$'
- done[n] = v.replace('$$', '$')
- else:
- done[n] = v
-
- # do variable interpolation here
- variables = list(notdone.keys())
-
- # Variables with a 'PY_' prefix in the makefile. These need to
- # be made available without that prefix through sysconfig.
- # Special care is needed to ensure that variable expansion works, even
- # if the expansion uses the name without a prefix.
- renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS')
-
- while len(variables) > 0:
- for name in tuple(variables):
- value = notdone[name]
- m = _findvar1_rx.search(value) or _findvar2_rx.search(value)
- if m is not None:
- n = m.group(1)
- found = True
- if n in done:
- item = str(done[n])
- elif n in notdone:
- # get it on a subsequent round
- found = False
- elif n in os.environ:
- # do it like make: fall back to environment
- item = os.environ[n]
-
- elif n in renamed_variables:
- if (name.startswith('PY_') and
- name[3:] in renamed_variables):
- item = ""
-
- elif 'PY_' + n in notdone:
- found = False
-
- else:
- item = str(done['PY_' + n])
-
- else:
- done[n] = item = ""
-
- if found:
- after = value[m.end():]
- value = value[:m.start()] + item + after
- if "$" in after:
- notdone[name] = value
- else:
- try:
- value = int(value)
- except ValueError:
- done[name] = value.strip()
- else:
- done[name] = value
- variables.remove(name)
-
- if (name.startswith('PY_') and
- name[3:] in renamed_variables):
-
- name = name[3:]
- if name not in done:
- done[name] = value
-
- else:
- # bogus variable reference (e.g. "prefix=$/opt/python");
- # just drop it since we can't deal
- done[name] = value
- variables.remove(name)
-
- # strip spurious spaces
- for k, v in done.items():
- if isinstance(v, str):
- done[k] = v.strip()
-
- # save the results in the global dictionary
- vars.update(done)
- return vars
-
-
-def get_makefile_filename():
- """Return the path of the Makefile."""
- if _PYTHON_BUILD:
- return os.path.join(_PROJECT_BASE, "Makefile")
- if hasattr(sys, 'abiflags'):
- config_dir_name = 'config-%s%s' % (_PY_VERSION_SHORT, sys.abiflags)
- else:
- config_dir_name = 'config'
- return os.path.join(get_path('stdlib'), config_dir_name, 'Makefile')
-
-
-def _init_posix(vars):
- """Initialize the module as appropriate for POSIX systems."""
- # load the installed Makefile:
- makefile = get_makefile_filename()
- try:
- _parse_makefile(makefile, vars)
- except IOError as e:
- msg = "invalid Python installation: unable to open %s" % makefile
- if hasattr(e, "strerror"):
- msg = msg + " (%s)" % e.strerror
- raise IOError(msg)
- # load the installed pyconfig.h:
- config_h = get_config_h_filename()
- try:
- with open(config_h) as f:
- parse_config_h(f, vars)
- except IOError as e:
- msg = "invalid Python installation: unable to open %s" % config_h
- if hasattr(e, "strerror"):
- msg = msg + " (%s)" % e.strerror
- raise IOError(msg)
- # On AIX, there are wrong paths to the linker scripts in the Makefile
- # -- these paths are relative to the Python source, but when installed
- # the scripts are in another directory.
- if _PYTHON_BUILD:
- vars['LDSHARED'] = vars['BLDSHARED']
-
-
-def _init_non_posix(vars):
- """Initialize the module as appropriate for NT"""
- # set basic install directories
- vars['LIBDEST'] = get_path('stdlib')
- vars['BINLIBDEST'] = get_path('platstdlib')
- vars['INCLUDEPY'] = get_path('include')
- vars['SO'] = '.pyd'
- vars['EXE'] = '.exe'
- vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT
- vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable))
-
-#
-# public APIs
-#
-
-
-def parse_config_h(fp, vars=None):
- """Parse a config.h-style file.
-
- A dictionary containing name/value pairs is returned. If an
- optional dictionary is passed in as the second argument, it is
- used instead of a new dictionary.
- """
- if vars is None:
- vars = {}
- define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n")
- undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n")
-
- while True:
- line = fp.readline()
- if not line:
- break
- m = define_rx.match(line)
- if m:
- n, v = m.group(1, 2)
- try:
- v = int(v)
- except ValueError:
- pass
- vars[n] = v
- else:
- m = undef_rx.match(line)
- if m:
- vars[m.group(1)] = 0
- return vars
-
-
-def get_config_h_filename():
- """Return the path of pyconfig.h."""
- if _PYTHON_BUILD:
- if os.name == "nt":
- inc_dir = os.path.join(_PROJECT_BASE, "PC")
- else:
- inc_dir = _PROJECT_BASE
- else:
- inc_dir = get_path('platinclude')
- return os.path.join(inc_dir, 'pyconfig.h')
-
-
-def get_scheme_names():
- """Return a tuple containing the schemes names."""
- return tuple(sorted(_SCHEMES.sections()))
-
-
-def get_path_names():
- """Return a tuple containing the paths names."""
- # xxx see if we want a static list
- return _SCHEMES.options('posix_prefix')
-
-
-def get_paths(scheme=_get_default_scheme(), vars=None, expand=True):
- """Return a mapping containing an install scheme.
-
- ``scheme`` is the install scheme name. If not provided, it will
- return the default scheme for the current platform.
- """
- _ensure_cfg_read()
- if expand:
- return _expand_vars(scheme, vars)
- else:
- return dict(_SCHEMES.items(scheme))
-
-
-def get_path(name, scheme=_get_default_scheme(), vars=None, expand=True):
- """Return a path corresponding to the scheme.
-
- ``scheme`` is the install scheme name.
- """
- return get_paths(scheme, vars, expand)[name]
-
-
-def get_config_vars(*args):
- """With no arguments, return a dictionary of all configuration
- variables relevant for the current platform.
-
- On Unix, this means every variable defined in Python's installed Makefile;
- On Windows and Mac OS it's a much smaller set.
-
- With arguments, return a list of values that result from looking up
- each argument in the configuration variable dictionary.
- """
- global _CONFIG_VARS
- if _CONFIG_VARS is None:
- _CONFIG_VARS = {}
- # Normalized versions of prefix and exec_prefix are handy to have;
- # in fact, these are the standard versions used most places in the
- # distutils2 module.
- _CONFIG_VARS['prefix'] = _PREFIX
- _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX
- _CONFIG_VARS['py_version'] = _PY_VERSION
- _CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT
- _CONFIG_VARS['py_version_nodot'] = _PY_VERSION[0] + _PY_VERSION[2]
- _CONFIG_VARS['base'] = _PREFIX
- _CONFIG_VARS['platbase'] = _EXEC_PREFIX
- _CONFIG_VARS['projectbase'] = _PROJECT_BASE
- try:
- _CONFIG_VARS['abiflags'] = sys.abiflags
- except AttributeError:
- # sys.abiflags may not be defined on all platforms.
- _CONFIG_VARS['abiflags'] = ''
-
- if os.name in ('nt', 'os2'):
- _init_non_posix(_CONFIG_VARS)
- if os.name == 'posix':
- _init_posix(_CONFIG_VARS)
- # Setting 'userbase' is done below the call to the
- # init function to enable using 'get_config_var' in
- # the init-function.
- if sys.version >= '2.6':
- _CONFIG_VARS['userbase'] = _getuserbase()
-
- if 'srcdir' not in _CONFIG_VARS:
- _CONFIG_VARS['srcdir'] = _PROJECT_BASE
- else:
- _CONFIG_VARS['srcdir'] = _safe_realpath(_CONFIG_VARS['srcdir'])
-
- # Convert srcdir into an absolute path if it appears necessary.
- # Normally it is relative to the build directory. However, during
- # testing, for example, we might be running a non-installed python
- # from a different directory.
- if _PYTHON_BUILD and os.name == "posix":
- base = _PROJECT_BASE
- try:
- cwd = os.getcwd()
- except OSError:
- cwd = None
- if (not os.path.isabs(_CONFIG_VARS['srcdir']) and
- base != cwd):
- # srcdir is relative and we are not in the same directory
- # as the executable. Assume executable is in the build
- # directory and make srcdir absolute.
- srcdir = os.path.join(base, _CONFIG_VARS['srcdir'])
- _CONFIG_VARS['srcdir'] = os.path.normpath(srcdir)
-
- if sys.platform == 'darwin':
- kernel_version = os.uname()[2] # Kernel version (8.4.3)
- major_version = int(kernel_version.split('.')[0])
-
- if major_version < 8:
- # On Mac OS X before 10.4, check if -arch and -isysroot
- # are in CFLAGS or LDFLAGS and remove them if they are.
- # This is needed when building extensions on a 10.3 system
- # using a universal build of python.
- for key in ('LDFLAGS', 'BASECFLAGS',
- # a number of derived variables. These need to be
- # patched up as well.
- 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
- flags = _CONFIG_VARS[key]
- flags = re.sub(r'-arch\s+\w+\s', ' ', flags)
- flags = re.sub('-isysroot [^ \t]*', ' ', flags)
- _CONFIG_VARS[key] = flags
- else:
- # Allow the user to override the architecture flags using
- # an environment variable.
- # NOTE: This name was introduced by Apple in OSX 10.5 and
- # is used by several scripting languages distributed with
- # that OS release.
- if 'ARCHFLAGS' in os.environ:
- arch = os.environ['ARCHFLAGS']
- for key in ('LDFLAGS', 'BASECFLAGS',
- # a number of derived variables. These need to be
- # patched up as well.
- 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
-
- flags = _CONFIG_VARS[key]
- flags = re.sub(r'-arch\s+\w+\s', ' ', flags)
- flags = flags + ' ' + arch
- _CONFIG_VARS[key] = flags
-
- # If we're on OSX 10.5 or later and the user tries to
- # compiles an extension using an SDK that is not present
- # on the current machine it is better to not use an SDK
- # than to fail.
- #
- # The major usecase for this is users using a Python.org
- # binary installer on OSX 10.6: that installer uses
- # the 10.4u SDK, but that SDK is not installed by default
- # when you install Xcode.
- #
- CFLAGS = _CONFIG_VARS.get('CFLAGS', '')
- m = re.search(r'-isysroot\s+(\S+)', CFLAGS)
- if m is not None:
- sdk = m.group(1)
- if not os.path.exists(sdk):
- for key in ('LDFLAGS', 'BASECFLAGS',
- # a number of derived variables. These need to be
- # patched up as well.
- 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
-
- flags = _CONFIG_VARS[key]
- flags = re.sub(r'-isysroot\s+\S+(\s|$)', ' ', flags)
- _CONFIG_VARS[key] = flags
-
- if args:
- vals = []
- for name in args:
- vals.append(_CONFIG_VARS.get(name))
- return vals
- else:
- return _CONFIG_VARS
-
-
-def get_config_var(name):
- """Return the value of a single variable using the dictionary returned by
- 'get_config_vars()'.
-
- Equivalent to get_config_vars().get(name)
- """
- return get_config_vars().get(name)
-
-
-def get_platform():
- """Return a string that identifies the current platform.
-
- This is used mainly to distinguish platform-specific build directories and
- platform-specific built distributions. Typically includes the OS name
- and version and the architecture (as supplied by 'os.uname()'),
- although the exact information included depends on the OS; eg. for IRIX
- the architecture isn't particularly important (IRIX only runs on SGI
- hardware), but for Linux the kernel version isn't particularly
- important.
-
- Examples of returned values:
- linux-i586
- linux-alpha (?)
- solaris-2.6-sun4u
- irix-5.3
- irix64-6.2
-
- Windows will return one of:
- win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
- win-ia64 (64bit Windows on Itanium)
- win32 (all others - specifically, sys.platform is returned)
-
- For other non-POSIX platforms, currently just returns 'sys.platform'.
- """
- if os.name == 'nt':
- # sniff sys.version for architecture.
- prefix = " bit ("
- i = sys.version.find(prefix)
- if i == -1:
- return sys.platform
- j = sys.version.find(")", i)
- look = sys.version[i+len(prefix):j].lower()
- if look == 'amd64':
- return 'win-amd64'
- if look == 'itanium':
- return 'win-ia64'
- return sys.platform
-
- if os.name != "posix" or not hasattr(os, 'uname'):
- # XXX what about the architecture? NT is Intel or Alpha,
- # Mac OS is M68k or PPC, etc.
- return sys.platform
-
- # Try to distinguish various flavours of Unix
- osname, host, release, version, machine = os.uname()
-
- # Convert the OS name to lowercase, remove '/' characters
- # (to accommodate BSD/OS), and translate spaces (for "Power Macintosh")
- osname = osname.lower().replace('/', '')
- machine = machine.replace(' ', '_')
- machine = machine.replace('/', '-')
-
- if osname[:5] == "linux":
- # At least on Linux/Intel, 'machine' is the processor --
- # i386, etc.
- # XXX what about Alpha, SPARC, etc?
- return "%s-%s" % (osname, machine)
- elif osname[:5] == "sunos":
- if release[0] >= "5": # SunOS 5 == Solaris 2
- osname = "solaris"
- release = "%d.%s" % (int(release[0]) - 3, release[2:])
- # fall through to standard osname-release-machine representation
- elif osname[:4] == "irix": # could be "irix64"!
- return "%s-%s" % (osname, release)
- elif osname[:3] == "aix":
- return "%s-%s.%s" % (osname, version, release)
- elif osname[:6] == "cygwin":
- osname = "cygwin"
- rel_re = re.compile(r'[\d.]+')
- m = rel_re.match(release)
- if m:
- release = m.group()
- elif osname[:6] == "darwin":
- #
- # For our purposes, we'll assume that the system version from
- # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set
- # to. This makes the compatibility story a bit more sane because the
- # machine is going to compile and link as if it were
- # MACOSX_DEPLOYMENT_TARGET.
- cfgvars = get_config_vars()
- macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET')
-
- if True:
- # Always calculate the release of the running machine,
- # needed to determine if we can build fat binaries or not.
-
- macrelease = macver
- # Get the system version. Reading this plist is a documented
- # way to get the system version (see the documentation for
- # the Gestalt Manager)
- try:
- f = open('/System/Library/CoreServices/SystemVersion.plist')
- except IOError:
- # We're on a plain darwin box, fall back to the default
- # behaviour.
- pass
- else:
- try:
- m = re.search(r'<key>ProductUserVisibleVersion</key>\s*'
- r'<string>(.*?)</string>', f.read())
- finally:
- f.close()
- if m is not None:
- macrelease = '.'.join(m.group(1).split('.')[:2])
- # else: fall back to the default behaviour
-
- if not macver:
- macver = macrelease
-
- if macver:
- release = macver
- osname = "macosx"
-
- if ((macrelease + '.') >= '10.4.' and
- '-arch' in get_config_vars().get('CFLAGS', '').strip()):
- # The universal build will build fat binaries, but not on
- # systems before 10.4
- #
- # Try to detect 4-way universal builds, those have machine-type
- # 'universal' instead of 'fat'.
-
- machine = 'fat'
- cflags = get_config_vars().get('CFLAGS')
-
- archs = re.findall(r'-arch\s+(\S+)', cflags)
- archs = tuple(sorted(set(archs)))
-
- if len(archs) == 1:
- machine = archs[0]
- elif archs == ('i386', 'ppc'):
- machine = 'fat'
- elif archs == ('i386', 'x86_64'):
- machine = 'intel'
- elif archs == ('i386', 'ppc', 'x86_64'):
- machine = 'fat3'
- elif archs == ('ppc64', 'x86_64'):
- machine = 'fat64'
- elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
- machine = 'universal'
- else:
- raise ValueError(
- "Don't know machine value for archs=%r" % (archs,))
-
- elif machine == 'i386':
- # On OSX the machine type returned by uname is always the
- # 32-bit variant, even if the executable architecture is
- # the 64-bit variant
- if sys.maxsize >= 2**32:
- machine = 'x86_64'
-
- elif machine in ('PowerPC', 'Power_Macintosh'):
- # Pick a sane name for the PPC architecture.
- # See 'i386' case
- if sys.maxsize >= 2**32:
- machine = 'ppc64'
- else:
- machine = 'ppc'
-
- return "%s-%s-%s" % (osname, release, machine)
-
-
-def get_python_version():
- return _PY_VERSION_SHORT
-
-
-def _print_dict(title, data):
- for index, (key, value) in enumerate(sorted(data.items())):
- if index == 0:
- print('%s: ' % (title))
- print('\t%s = "%s"' % (key, value))
-
-
-def _main():
- """Display all information sysconfig detains."""
- print('Platform: "%s"' % get_platform())
- print('Python version: "%s"' % get_python_version())
- print('Current installation scheme: "%s"' % _get_default_scheme())
- print()
- _print_dict('Paths', get_paths())
- print()
- _print_dict('Variables', get_config_vars())
-
-
-if __name__ == '__main__':
- _main()
diff --git a/src/pip/_vendor/distlib/_backport/tarfile.py b/src/pip/_vendor/distlib/_backport/tarfile.py
deleted file mode 100644
index d66d85663..000000000
--- a/src/pip/_vendor/distlib/_backport/tarfile.py
+++ /dev/null
@@ -1,2607 +0,0 @@
-#-------------------------------------------------------------------
-# tarfile.py
-#-------------------------------------------------------------------
-# Copyright (C) 2002 Lars Gustaebel <lars@gustaebel.de>
-# All rights reserved.
-#
-# 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.
-#
-from __future__ import print_function
-
-"""Read from and write to tar format archives.
-"""
-
-__version__ = "$Revision$"
-
-version = "0.9.0"
-__author__ = "Lars Gust\u00e4bel (lars@gustaebel.de)"
-__date__ = "$Date: 2011-02-25 17:42:01 +0200 (Fri, 25 Feb 2011) $"
-__cvsid__ = "$Id: tarfile.py 88586 2011-02-25 15:42:01Z marc-andre.lemburg $"
-__credits__ = "Gustavo Niemeyer, Niels Gust\u00e4bel, Richard Townsend."
-
-#---------
-# Imports
-#---------
-import sys
-import os
-import stat
-import errno
-import time
-import struct
-import copy
-import re
-
-try:
- import grp, pwd
-except ImportError:
- grp = pwd = None
-
-# os.symlink on Windows prior to 6.0 raises NotImplementedError
-symlink_exception = (AttributeError, NotImplementedError)
-try:
- # WindowsError (1314) will be raised if the caller does not hold the
- # SeCreateSymbolicLinkPrivilege privilege
- symlink_exception += (WindowsError,)
-except NameError:
- pass
-
-# from tarfile import *
-__all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError"]
-
-if sys.version_info[0] < 3:
- import __builtin__ as builtins
-else:
- import builtins
-
-_open = builtins.open # Since 'open' is TarFile.open
-
-#---------------------------------------------------------
-# tar constants
-#---------------------------------------------------------
-NUL = b"\0" # the null character
-BLOCKSIZE = 512 # length of processing blocks
-RECORDSIZE = BLOCKSIZE * 20 # length of records
-GNU_MAGIC = b"ustar \0" # magic gnu tar string
-POSIX_MAGIC = b"ustar\x0000" # magic posix tar string
-
-LENGTH_NAME = 100 # maximum length of a filename
-LENGTH_LINK = 100 # maximum length of a linkname
-LENGTH_PREFIX = 155 # maximum length of the prefix field
-
-REGTYPE = b"0" # regular file
-AREGTYPE = b"\0" # regular file
-LNKTYPE = b"1" # link (inside tarfile)
-SYMTYPE = b"2" # symbolic link
-CHRTYPE = b"3" # character special device
-BLKTYPE = b"4" # block special device
-DIRTYPE = b"5" # directory
-FIFOTYPE = b"6" # fifo special device
-CONTTYPE = b"7" # contiguous file
-
-GNUTYPE_LONGNAME = b"L" # GNU tar longname
-GNUTYPE_LONGLINK = b"K" # GNU tar longlink
-GNUTYPE_SPARSE = b"S" # GNU tar sparse file
-
-XHDTYPE = b"x" # POSIX.1-2001 extended header
-XGLTYPE = b"g" # POSIX.1-2001 global header
-SOLARIS_XHDTYPE = b"X" # Solaris extended header
-
-USTAR_FORMAT = 0 # POSIX.1-1988 (ustar) format
-GNU_FORMAT = 1 # GNU tar format
-PAX_FORMAT = 2 # POSIX.1-2001 (pax) format
-DEFAULT_FORMAT = GNU_FORMAT
-
-#---------------------------------------------------------
-# tarfile constants
-#---------------------------------------------------------
-# File types that tarfile supports:
-SUPPORTED_TYPES = (REGTYPE, AREGTYPE, LNKTYPE,
- SYMTYPE, DIRTYPE, FIFOTYPE,
- CONTTYPE, CHRTYPE, BLKTYPE,
- GNUTYPE_LONGNAME, GNUTYPE_LONGLINK,
- GNUTYPE_SPARSE)
-
-# File types that will be treated as a regular file.
-REGULAR_TYPES = (REGTYPE, AREGTYPE,
- CONTTYPE, GNUTYPE_SPARSE)
-
-# File types that are part of the GNU tar format.
-GNU_TYPES = (GNUTYPE_LONGNAME, GNUTYPE_LONGLINK,
- GNUTYPE_SPARSE)
-
-# Fields from a pax header that override a TarInfo attribute.
-PAX_FIELDS = ("path", "linkpath", "size", "mtime",
- "uid", "gid", "uname", "gname")
-
-# Fields from a pax header that are affected by hdrcharset.
-PAX_NAME_FIELDS = set(("path", "linkpath", "uname", "gname"))
-
-# Fields in a pax header that are numbers, all other fields
-# are treated as strings.
-PAX_NUMBER_FIELDS = {
- "atime": float,
- "ctime": float,
- "mtime": float,
- "uid": int,
- "gid": int,
- "size": int
-}
-
-#---------------------------------------------------------
-# Bits used in the mode field, values in octal.
-#---------------------------------------------------------
-S_IFLNK = 0o120000 # symbolic link
-S_IFREG = 0o100000 # regular file
-S_IFBLK = 0o060000 # block device
-S_IFDIR = 0o040000 # directory
-S_IFCHR = 0o020000 # character device
-S_IFIFO = 0o010000 # fifo
-
-TSUID = 0o4000 # set UID on execution
-TSGID = 0o2000 # set GID on execution
-TSVTX = 0o1000 # reserved
-
-TUREAD = 0o400 # read by owner
-TUWRITE = 0o200 # write by owner
-TUEXEC = 0o100 # execute/search by owner
-TGREAD = 0o040 # read by group
-TGWRITE = 0o020 # write by group
-TGEXEC = 0o010 # execute/search by group
-TOREAD = 0o004 # read by other
-TOWRITE = 0o002 # write by other
-TOEXEC = 0o001 # execute/search by other
-
-#---------------------------------------------------------
-# initialization
-#---------------------------------------------------------
-if os.name in ("nt", "ce"):
- ENCODING = "utf-8"
-else:
- ENCODING = sys.getfilesystemencoding()
-
-#---------------------------------------------------------
-# Some useful functions
-#---------------------------------------------------------
-
-def stn(s, length, encoding, errors):
- """Convert a string to a null-terminated bytes object.
- """
- s = s.encode(encoding, errors)
- return s[:length] + (length - len(s)) * NUL
-
-def nts(s, encoding, errors):
- """Convert a null-terminated bytes object to a string.
- """
- p = s.find(b"\0")
- if p != -1:
- s = s[:p]
- return s.decode(encoding, errors)
-
-def nti(s):
- """Convert a number field to a python number.
- """
- # There are two possible encodings for a number field, see
- # itn() below.
- if s[0] != chr(0o200):
- try:
- n = int(nts(s, "ascii", "strict") or "0", 8)
- except ValueError:
- raise InvalidHeaderError("invalid header")
- else:
- n = 0
- for i in range(len(s) - 1):
- n <<= 8
- n += ord(s[i + 1])
- return n
-
-def itn(n, digits=8, format=DEFAULT_FORMAT):
- """Convert a python number to a number field.
- """
- # POSIX 1003.1-1988 requires numbers to be encoded as a string of
- # octal digits followed by a null-byte, this allows values up to
- # (8**(digits-1))-1. GNU tar allows storing numbers greater than
- # that if necessary. A leading 0o200 byte indicates this particular
- # encoding, the following digits-1 bytes are a big-endian
- # representation. This allows values up to (256**(digits-1))-1.
- if 0 <= n < 8 ** (digits - 1):
- s = ("%0*o" % (digits - 1, n)).encode("ascii") + NUL
- else:
- if format != GNU_FORMAT or n >= 256 ** (digits - 1):
- raise ValueError("overflow in number field")
-
- if n < 0:
- # XXX We mimic GNU tar's behaviour with negative numbers,
- # this could raise OverflowError.
- n = struct.unpack("L", struct.pack("l", n))[0]
-
- s = bytearray()
- for i in range(digits - 1):
- s.insert(0, n & 0o377)
- n >>= 8
- s.insert(0, 0o200)
- return s
-
-def calc_chksums(buf):
- """Calculate the checksum for a member's header by summing up all
- characters except for the chksum field which is treated as if
- it was filled with spaces. According to the GNU tar sources,
- some tars (Sun and NeXT) calculate chksum with signed char,
- which will be different if there are chars in the buffer with
- the high bit set. So we calculate two checksums, unsigned and
- signed.
- """
- unsigned_chksum = 256 + sum(struct.unpack("148B", buf[:148]) + struct.unpack("356B", buf[156:512]))
- signed_chksum = 256 + sum(struct.unpack("148b", buf[:148]) + struct.unpack("356b", buf[156:512]))
- return unsigned_chksum, signed_chksum
-
-def copyfileobj(src, dst, length=None):
- """Copy length bytes from fileobj src to fileobj dst.
- If length is None, copy the entire content.
- """
- if length == 0:
- return
- if length is None:
- while True:
- buf = src.read(16*1024)
- if not buf:
- break
- dst.write(buf)
- return
-
- BUFSIZE = 16 * 1024
- blocks, remainder = divmod(length, BUFSIZE)
- for b in range(blocks):
- buf = src.read(BUFSIZE)
- if len(buf) < BUFSIZE:
- raise IOError("end of file reached")
- dst.write(buf)
-
- if remainder != 0:
- buf = src.read(remainder)
- if len(buf) < remainder:
- raise IOError("end of file reached")
- dst.write(buf)
- return
-
-filemode_table = (
- ((S_IFLNK, "l"),
- (S_IFREG, "-"),
- (S_IFBLK, "b"),
- (S_IFDIR, "d"),
- (S_IFCHR, "c"),
- (S_IFIFO, "p")),
-
- ((TUREAD, "r"),),
- ((TUWRITE, "w"),),
- ((TUEXEC|TSUID, "s"),
- (TSUID, "S"),
- (TUEXEC, "x")),
-
- ((TGREAD, "r"),),
- ((TGWRITE, "w"),),
- ((TGEXEC|TSGID, "s"),
- (TSGID, "S"),
- (TGEXEC, "x")),
-
- ((TOREAD, "r"),),
- ((TOWRITE, "w"),),
- ((TOEXEC|TSVTX, "t"),
- (TSVTX, "T"),
- (TOEXEC, "x"))
-)
-
-def filemode(mode):
- """Convert a file's mode to a string of the form
- -rwxrwxrwx.
- Used by TarFile.list()
- """
- perm = []
- for table in filemode_table:
- for bit, char in table:
- if mode & bit == bit:
- perm.append(char)
- break
- else:
- perm.append("-")
- return "".join(perm)
-
-class TarError(Exception):
- """Base exception."""
- pass
-class ExtractError(TarError):
- """General exception for extract errors."""
- pass
-class ReadError(TarError):
- """Exception for unreadable tar archives."""
- pass
-class CompressionError(TarError):
- """Exception for unavailable compression methods."""
- pass
-class StreamError(TarError):
- """Exception for unsupported operations on stream-like TarFiles."""
- pass
-class HeaderError(TarError):
- """Base exception for header errors."""
- pass
-class EmptyHeaderError(HeaderError):
- """Exception for empty headers."""
- pass
-class TruncatedHeaderError(HeaderError):
- """Exception for truncated headers."""
- pass
-class EOFHeaderError(HeaderError):
- """Exception for end of file headers."""
- pass
-class InvalidHeaderError(HeaderError):
- """Exception for invalid headers."""
- pass
-class SubsequentHeaderError(HeaderError):
- """Exception for missing and invalid extended headers."""
- pass
-
-#---------------------------
-# internal stream interface
-#---------------------------
-class _LowLevelFile(object):
- """Low-level file object. Supports reading and writing.
- It is used instead of a regular file object for streaming
- access.
- """
-
- def __init__(self, name, mode):
- mode = {
- "r": os.O_RDONLY,
- "w": os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
- }[mode]
- if hasattr(os, "O_BINARY"):
- mode |= os.O_BINARY
- self.fd = os.open(name, mode, 0o666)
-
- def close(self):
- os.close(self.fd)
-
- def read(self, size):
- return os.read(self.fd, size)
-
- def write(self, s):
- os.write(self.fd, s)
-
-class _Stream(object):
- """Class that serves as an adapter between TarFile and
- a stream-like object. The stream-like object only
- needs to have a read() or write() method and is accessed
- blockwise. Use of gzip or bzip2 compression is possible.
- A stream-like object could be for example: sys.stdin,
- sys.stdout, a socket, a tape device etc.
-
- _Stream is intended to be used only internally.
- """
-
- def __init__(self, name, mode, comptype, fileobj, bufsize):
- """Construct a _Stream object.
- """
- self._extfileobj = True
- if fileobj is None:
- fileobj = _LowLevelFile(name, mode)
- self._extfileobj = False
-
- if comptype == '*':
- # Enable transparent compression detection for the
- # stream interface
- fileobj = _StreamProxy(fileobj)
- comptype = fileobj.getcomptype()
-
- self.name = name or ""
- self.mode = mode
- self.comptype = comptype
- self.fileobj = fileobj
- self.bufsize = bufsize
- self.buf = b""
- self.pos = 0
- self.closed = False
-
- try:
- if comptype == "gz":
- try:
- import zlib
- except ImportError:
- raise CompressionError("zlib module is not available")
- self.zlib = zlib
- self.crc = zlib.crc32(b"")
- if mode == "r":
- self._init_read_gz()
- else:
- self._init_write_gz()
-
- if comptype == "bz2":
- try:
- import bz2
- except ImportError:
- raise CompressionError("bz2 module is not available")
- if mode == "r":
- self.dbuf = b""
- self.cmp = bz2.BZ2Decompressor()
- else:
- self.cmp = bz2.BZ2Compressor()
- except:
- if not self._extfileobj:
- self.fileobj.close()
- self.closed = True
- raise
-
- def __del__(self):
- if hasattr(self, "closed") and not self.closed:
- self.close()
-
- def _init_write_gz(self):
- """Initialize for writing with gzip compression.
- """
- self.cmp = self.zlib.compressobj(9, self.zlib.DEFLATED,
- -self.zlib.MAX_WBITS,
- self.zlib.DEF_MEM_LEVEL,
- 0)
- timestamp = struct.pack("<L", int(time.time()))
- self.__write(b"\037\213\010\010" + timestamp + b"\002\377")
- if self.name.endswith(".gz"):
- self.name = self.name[:-3]
- # RFC1952 says we must use ISO-8859-1 for the FNAME field.
- self.__write(self.name.encode("iso-8859-1", "replace") + NUL)
-
- def write(self, s):
- """Write string s to the stream.
- """
- if self.comptype == "gz":
- self.crc = self.zlib.crc32(s, self.crc)
- self.pos += len(s)
- if self.comptype != "tar":
- s = self.cmp.compress(s)
- self.__write(s)
-
- def __write(self, s):
- """Write string s to the stream if a whole new block
- is ready to be written.
- """
- self.buf += s
- while len(self.buf) > self.bufsize:
- self.fileobj.write(self.buf[:self.bufsize])
- self.buf = self.buf[self.bufsize:]
-
- def close(self):
- """Close the _Stream object. No operation should be
- done on it afterwards.
- """
- if self.closed:
- return
-
- if self.mode == "w" and self.comptype != "tar":
- self.buf += self.cmp.flush()
-
- if self.mode == "w" and self.buf:
- self.fileobj.write(self.buf)
- self.buf = b""
- if self.comptype == "gz":
- # The native zlib crc is an unsigned 32-bit integer, but
- # the Python wrapper implicitly casts that to a signed C
- # long. So, on a 32-bit box self.crc may "look negative",
- # while the same crc on a 64-bit box may "look positive".
- # To avoid irksome warnings from the `struct` module, force
- # it to look positive on all boxes.
- self.fileobj.write(struct.pack("<L", self.crc & 0xffffffff))
- self.fileobj.write(struct.pack("<L", self.pos & 0xffffFFFF))
-
- if not self._extfileobj:
- self.fileobj.close()
-
- self.closed = True
-
- def _init_read_gz(self):
- """Initialize for reading a gzip compressed fileobj.
- """
- self.cmp = self.zlib.decompressobj(-self.zlib.MAX_WBITS)
- self.dbuf = b""
-
- # taken from gzip.GzipFile with some alterations
- if self.__read(2) != b"\037\213":
- raise ReadError("not a gzip file")
- if self.__read(1) != b"\010":
- raise CompressionError("unsupported compression method")
-
- flag = ord(self.__read(1))
- self.__read(6)
-
- if flag & 4:
- xlen = ord(self.__read(1)) + 256 * ord(self.__read(1))
- self.read(xlen)
- if flag & 8:
- while True:
- s = self.__read(1)
- if not s or s == NUL:
- break
- if flag & 16:
- while True:
- s = self.__read(1)
- if not s or s == NUL:
- break
- if flag & 2:
- self.__read(2)
-
- def tell(self):
- """Return the stream's file pointer position.
- """
- return self.pos
-
- def seek(self, pos=0):
- """Set the stream's file pointer to pos. Negative seeking
- is forbidden.
- """
- if pos - self.pos >= 0:
- blocks, remainder = divmod(pos - self.pos, self.bufsize)
- for i in range(blocks):
- self.read(self.bufsize)
- self.read(remainder)
- else:
- raise StreamError("seeking backwards is not allowed")
- return self.pos
-
- def read(self, size=None):
- """Return the next size number of bytes from the stream.
- If size is not defined, return all bytes of the stream
- up to EOF.
- """
- if size is None:
- t = []
- while True:
- buf = self._read(self.bufsize)
- if not buf:
- break
- t.append(buf)
- buf = "".join(t)
- else:
- buf = self._read(size)
- self.pos += len(buf)
- return buf
-
- def _read(self, size):
- """Return size bytes from the stream.
- """
- if self.comptype == "tar":
- return self.__read(size)
-
- c = len(self.dbuf)
- while c < size:
- buf = self.__read(self.bufsize)
- if not buf:
- break
- try:
- buf = self.cmp.decompress(buf)
- except IOError:
- raise ReadError("invalid compressed data")
- self.dbuf += buf
- c += len(buf)
- buf = self.dbuf[:size]
- self.dbuf = self.dbuf[size:]
- return buf
-
- def __read(self, size):
- """Return size bytes from stream. If internal buffer is empty,
- read another block from the stream.
- """
- c = len(self.buf)
- while c < size:
- buf = self.fileobj.read(self.bufsize)
- if not buf:
- break
- self.buf += buf
- c += len(buf)
- buf = self.buf[:size]
- self.buf = self.buf[size:]
- return buf
-# class _Stream
-
-class _StreamProxy(object):
- """Small proxy class that enables transparent compression
- detection for the Stream interface (mode 'r|*').
- """
-
- def __init__(self, fileobj):
- self.fileobj = fileobj
- self.buf = self.fileobj.read(BLOCKSIZE)
-
- def read(self, size):
- self.read = self.fileobj.read
- return self.buf
-
- def getcomptype(self):
- if self.buf.startswith(b"\037\213\010"):
- return "gz"
- if self.buf.startswith(b"BZh91"):
- return "bz2"
- return "tar"
-
- def close(self):
- self.fileobj.close()
-# class StreamProxy
-
-class _BZ2Proxy(object):
- """Small proxy class that enables external file object
- support for "r:bz2" and "w:bz2" modes. This is actually
- a workaround for a limitation in bz2 module's BZ2File
- class which (unlike gzip.GzipFile) has no support for
- a file object argument.
- """
-
- blocksize = 16 * 1024
-
- def __init__(self, fileobj, mode):
- self.fileobj = fileobj
- self.mode = mode
- self.name = getattr(self.fileobj, "name", None)
- self.init()
-
- def init(self):
- import bz2
- self.pos = 0
- if self.mode == "r":
- self.bz2obj = bz2.BZ2Decompressor()
- self.fileobj.seek(0)
- self.buf = b""
- else:
- self.bz2obj = bz2.BZ2Compressor()
-
- def read(self, size):
- x = len(self.buf)
- while x < size:
- raw = self.fileobj.read(self.blocksize)
- if not raw:
- break
- data = self.bz2obj.decompress(raw)
- self.buf += data
- x += len(data)
-
- buf = self.buf[:size]
- self.buf = self.buf[size:]
- self.pos += len(buf)
- return buf
-
- def seek(self, pos):
- if pos < self.pos:
- self.init()
- self.read(pos - self.pos)
-
- def tell(self):
- return self.pos
-
- def write(self, data):
- self.pos += len(data)
- raw = self.bz2obj.compress(data)
- self.fileobj.write(raw)
-
- def close(self):
- if self.mode == "w":
- raw = self.bz2obj.flush()
- self.fileobj.write(raw)
-# class _BZ2Proxy
-
-#------------------------
-# Extraction file object
-#------------------------
-class _FileInFile(object):
- """A thin wrapper around an existing file object that
- provides a part of its data as an individual file
- object.
- """
-
- def __init__(self, fileobj, offset, size, blockinfo=None):
- self.fileobj = fileobj
- self.offset = offset
- self.size = size
- self.position = 0
-
- if blockinfo is None:
- blockinfo = [(0, size)]
-
- # Construct a map with data and zero blocks.
- self.map_index = 0
- self.map = []
- lastpos = 0
- realpos = self.offset
- for offset, size in blockinfo:
- if offset > lastpos:
- self.map.append((False, lastpos, offset, None))
- self.map.append((True, offset, offset + size, realpos))
- realpos += size
- lastpos = offset + size
- if lastpos < self.size:
- self.map.append((False, lastpos, self.size, None))
-
- def seekable(self):
- if not hasattr(self.fileobj, "seekable"):
- # XXX gzip.GzipFile and bz2.BZ2File
- return True
- return self.fileobj.seekable()
-
- def tell(self):
- """Return the current file position.
- """
- return self.position
-
- def seek(self, position):
- """Seek to a position in the file.
- """
- self.position = position
-
- def read(self, size=None):
- """Read data from the file.
- """
- if size is None:
- size = self.size - self.position
- else:
- size = min(size, self.size - self.position)
-
- buf = b""
- while size > 0:
- while True:
- data, start, stop, offset = self.map[self.map_index]
- if start <= self.position < stop:
- break
- else:
- self.map_index += 1
- if self.map_index == len(self.map):
- self.map_index = 0
- length = min(size, stop - self.position)
- if data:
- self.fileobj.seek(offset + (self.position - start))
- buf += self.fileobj.read(length)
- else:
- buf += NUL * length
- size -= length
- self.position += length
- return buf
-#class _FileInFile
-
-
-class ExFileObject(object):
- """File-like object for reading an archive member.
- Is returned by TarFile.extractfile().
- """
- blocksize = 1024
-
- def __init__(self, tarfile, tarinfo):
- self.fileobj = _FileInFile(tarfile.fileobj,
- tarinfo.offset_data,
- tarinfo.size,
- tarinfo.sparse)
- self.name = tarinfo.name
- self.mode = "r"
- self.closed = False
- self.size = tarinfo.size
-
- self.position = 0
- self.buffer = b""
-
- def readable(self):
- return True
-
- def writable(self):
- return False
-
- def seekable(self):
- return self.fileobj.seekable()
-
- def read(self, size=None):
- """Read at most size bytes from the file. If size is not
- present or None, read all data until EOF is reached.
- """
- if self.closed:
- raise ValueError("I/O operation on closed file")
-
- buf = b""
- if self.buffer:
- if size is None:
- buf = self.buffer
- self.buffer = b""
- else:
- buf = self.buffer[:size]
- self.buffer = self.buffer[size:]
-
- if size is None:
- buf += self.fileobj.read()
- else:
- buf += self.fileobj.read(size - len(buf))
-
- self.position += len(buf)
- return buf
-
- # XXX TextIOWrapper uses the read1() method.
- read1 = read
-
- def readline(self, size=-1):
- """Read one entire line from the file. If size is present
- and non-negative, return a string with at most that
- size, which may be an incomplete line.
- """
- if self.closed:
- raise ValueError("I/O operation on closed file")
-
- pos = self.buffer.find(b"\n") + 1
- if pos == 0:
- # no newline found.
- while True:
- buf = self.fileobj.read(self.blocksize)
- self.buffer += buf
- if not buf or b"\n" in buf:
- pos = self.buffer.find(b"\n") + 1
- if pos == 0:
- # no newline found.
- pos = len(self.buffer)
- break
-
- if size != -1:
- pos = min(size, pos)
-
- buf = self.buffer[:pos]
- self.buffer = self.buffer[pos:]
- self.position += len(buf)
- return buf
-
- def readlines(self):
- """Return a list with all remaining lines.
- """
- result = []
- while True:
- line = self.readline()
- if not line: break
- result.append(line)
- return result
-
- def tell(self):
- """Return the current file position.
- """
- if self.closed:
- raise ValueError("I/O operation on closed file")
-
- return self.position
-
- def seek(self, pos, whence=os.SEEK_SET):
- """Seek to a position in the file.
- """
- if self.closed:
- raise ValueError("I/O operation on closed file")
-
- if whence == os.SEEK_SET:
- self.position = min(max(pos, 0), self.size)
- elif whence == os.SEEK_CUR:
- if pos < 0:
- self.position = max(self.position + pos, 0)
- else:
- self.position = min(self.position + pos, self.size)
- elif whence == os.SEEK_END:
- self.position = max(min(self.size + pos, self.size), 0)
- else:
- raise ValueError("Invalid argument")
-
- self.buffer = b""
- self.fileobj.seek(self.position)
-
- def close(self):
- """Close the file object.
- """
- self.closed = True
-
- def __iter__(self):
- """Get an iterator over the file's lines.
- """
- while True:
- line = self.readline()
- if not line:
- break
- yield line
-#class ExFileObject
-
-#------------------
-# Exported Classes
-#------------------
-class TarInfo(object):
- """Informational class which holds the details about an
- archive member given by a tar header block.
- TarInfo objects are returned by TarFile.getmember(),
- TarFile.getmembers() and TarFile.gettarinfo() and are
- usually created internally.
- """
-
- __slots__ = ("name", "mode", "uid", "gid", "size", "mtime",
- "chksum", "type", "linkname", "uname", "gname",
- "devmajor", "devminor",
- "offset", "offset_data", "pax_headers", "sparse",
- "tarfile", "_sparse_structs", "_link_target")
-
- def __init__(self, name=""):
- """Construct a TarInfo object. name is the optional name
- of the member.
- """
- self.name = name # member name
- self.mode = 0o644 # file permissions
- self.uid = 0 # user id
- self.gid = 0 # group id
- self.size = 0 # file size
- self.mtime = 0 # modification time
- self.chksum = 0 # header checksum
- self.type = REGTYPE # member type
- self.linkname = "" # link name
- self.uname = "" # user name
- self.gname = "" # group name
- self.devmajor = 0 # device major number
- self.devminor = 0 # device minor number
-
- self.offset = 0 # the tar header starts here
- self.offset_data = 0 # the file's data starts here
-
- self.sparse = None # sparse member information
- self.pax_headers = {} # pax header information
-
- # In pax headers the "name" and "linkname" field are called
- # "path" and "linkpath".
- def _getpath(self):
- return self.name
- def _setpath(self, name):
- self.name = name
- path = property(_getpath, _setpath)
-
- def _getlinkpath(self):
- return self.linkname
- def _setlinkpath(self, linkname):
- self.linkname = linkname
- linkpath = property(_getlinkpath, _setlinkpath)
-
- def __repr__(self):
- return "<%s %r at %#x>" % (self.__class__.__name__,self.name,id(self))
-
- def get_info(self):
- """Return the TarInfo's attributes as a dictionary.
- """
- info = {
- "name": self.name,
- "mode": self.mode & 0o7777,
- "uid": self.uid,
- "gid": self.gid,
- "size": self.size,
- "mtime": self.mtime,
- "chksum": self.chksum,
- "type": self.type,
- "linkname": self.linkname,
- "uname": self.uname,
- "gname": self.gname,
- "devmajor": self.devmajor,
- "devminor": self.devminor
- }
-
- if info["type"] == DIRTYPE and not info["name"].endswith("/"):
- info["name"] += "/"
-
- return info
-
- def tobuf(self, format=DEFAULT_FORMAT, encoding=ENCODING, errors="surrogateescape"):
- """Return a tar header as a string of 512 byte blocks.
- """
- info = self.get_info()
-
- if format == USTAR_FORMAT:
- return self.create_ustar_header(info, encoding, errors)
- elif format == GNU_FORMAT:
- return self.create_gnu_header(info, encoding, errors)
- elif format == PAX_FORMAT:
- return self.create_pax_header(info, encoding)
- else:
- raise ValueError("invalid format")
-
- def create_ustar_header(self, info, encoding, errors):
- """Return the object as a ustar header block.
- """
- info["magic"] = POSIX_MAGIC
-
- if len(info["linkname"]) > LENGTH_LINK:
- raise ValueError("linkname is too long")
-
- if len(info["name"]) > LENGTH_NAME:
- info["prefix"], info["name"] = self._posix_split_name(info["name"])
-
- return self._create_header(info, USTAR_FORMAT, encoding, errors)
-
- def create_gnu_header(self, info, encoding, errors):
- """Return the object as a GNU header block sequence.
- """
- info["magic"] = GNU_MAGIC
-
- buf = b""
- if len(info["linkname"]) > LENGTH_LINK:
- buf += self._create_gnu_long_header(info["linkname"], GNUTYPE_LONGLINK, encoding, errors)
-
- if len(info["name"]) > LENGTH_NAME:
- buf += self._create_gnu_long_header(info["name"], GNUTYPE_LONGNAME, encoding, errors)
-
- return buf + self._create_header(info, GNU_FORMAT, encoding, errors)
-
- def create_pax_header(self, info, encoding):
- """Return the object as a ustar header block. If it cannot be
- represented this way, prepend a pax extended header sequence
- with supplement information.
- """
- info["magic"] = POSIX_MAGIC
- pax_headers = self.pax_headers.copy()
-
- # Test string fields for values that exceed the field length or cannot
- # be represented in ASCII encoding.
- for name, hname, length in (
- ("name", "path", LENGTH_NAME), ("linkname", "linkpath", LENGTH_LINK),
- ("uname", "uname", 32), ("gname", "gname", 32)):
-
- if hname in pax_headers:
- # The pax header has priority.
- continue
-
- # Try to encode the string as ASCII.
- try:
- info[name].encode("ascii", "strict")
- except UnicodeEncodeError:
- pax_headers[hname] = info[name]
- continue
-
- if len(info[name]) > length:
- pax_headers[hname] = info[name]
-
- # Test number fields for values that exceed the field limit or values
- # that like to be stored as float.
- for name, digits in (("uid", 8), ("gid", 8), ("size", 12), ("mtime", 12)):
- if name in pax_headers:
- # The pax header has priority. Avoid overflow.
- info[name] = 0
- continue
-
- val = info[name]
- if not 0 <= val < 8 ** (digits - 1) or isinstance(val, float):
- pax_headers[name] = str(val)
- info[name] = 0
-
- # Create a pax extended header if necessary.
- if pax_headers:
- buf = self._create_pax_generic_header(pax_headers, XHDTYPE, encoding)
- else:
- buf = b""
-
- return buf + self._create_header(info, USTAR_FORMAT, "ascii", "replace")
-
- @classmethod
- def create_pax_global_header(cls, pax_headers):
- """Return the object as a pax global header block sequence.
- """
- return cls._create_pax_generic_header(pax_headers, XGLTYPE, "utf8")
-
- def _posix_split_name(self, name):
- """Split a name longer than 100 chars into a prefix
- and a name part.
- """
- prefix = name[:LENGTH_PREFIX + 1]
- while prefix and prefix[-1] != "/":
- prefix = prefix[:-1]
-
- name = name[len(prefix):]
- prefix = prefix[:-1]
-
- if not prefix or len(name) > LENGTH_NAME:
- raise ValueError("name is too long")
- return prefix, name
-
- @staticmethod
- def _create_header(info, format, encoding, errors):
- """Return a header block. info is a dictionary with file
- information, format must be one of the *_FORMAT constants.
- """
- parts = [
- stn(info.get("name", ""), 100, encoding, errors),
- itn(info.get("mode", 0) & 0o7777, 8, format),
- itn(info.get("uid", 0), 8, format),
- itn(info.get("gid", 0), 8, format),
- itn(info.get("size", 0), 12, format),
- itn(info.get("mtime", 0), 12, format),
- b" ", # checksum field
- info.get("type", REGTYPE),
- stn(info.get("linkname", ""), 100, encoding, errors),
- info.get("magic", POSIX_MAGIC),
- stn(info.get("uname", ""), 32, encoding, errors),
- stn(info.get("gname", ""), 32, encoding, errors),
- itn(info.get("devmajor", 0), 8, format),
- itn(info.get("devminor", 0), 8, format),
- stn(info.get("prefix", ""), 155, encoding, errors)
- ]
-
- buf = struct.pack("%ds" % BLOCKSIZE, b"".join(parts))
- chksum = calc_chksums(buf[-BLOCKSIZE:])[0]
- buf = buf[:-364] + ("%06o\0" % chksum).encode("ascii") + buf[-357:]
- return buf
-
- @staticmethod
- def _create_payload(payload):
- """Return the string payload filled with zero bytes
- up to the next 512 byte border.
- """
- blocks, remainder = divmod(len(payload), BLOCKSIZE)
- if remainder > 0:
- payload += (BLOCKSIZE - remainder) * NUL
- return payload
-
- @classmethod
- def _create_gnu_long_header(cls, name, type, encoding, errors):
- """Return a GNUTYPE_LONGNAME or GNUTYPE_LONGLINK sequence
- for name.
- """
- name = name.encode(encoding, errors) + NUL
-
- info = {}
- info["name"] = "././@LongLink"
- info["type"] = type
- info["size"] = len(name)
- info["magic"] = GNU_MAGIC
-
- # create extended header + name blocks.
- return cls._create_header(info, USTAR_FORMAT, encoding, errors) + \
- cls._create_payload(name)
-
- @classmethod
- def _create_pax_generic_header(cls, pax_headers, type, encoding):
- """Return a POSIX.1-2008 extended or global header sequence
- that contains a list of keyword, value pairs. The values
- must be strings.
- """
- # Check if one of the fields contains surrogate characters and thereby
- # forces hdrcharset=BINARY, see _proc_pax() for more information.
- binary = False
- for keyword, value in pax_headers.items():
- try:
- value.encode("utf8", "strict")
- except UnicodeEncodeError:
- binary = True
- break
-
- records = b""
- if binary:
- # Put the hdrcharset field at the beginning of the header.
- records += b"21 hdrcharset=BINARY\n"
-
- for keyword, value in pax_headers.items():
- keyword = keyword.encode("utf8")
- if binary:
- # Try to restore the original byte representation of `value'.
- # Needless to say, that the encoding must match the string.
- value = value.encode(encoding, "surrogateescape")
- else:
- value = value.encode("utf8")
-
- l = len(keyword) + len(value) + 3 # ' ' + '=' + '\n'
- n = p = 0
- while True:
- n = l + len(str(p))
- if n == p:
- break
- p = n
- records += bytes(str(p), "ascii") + b" " + keyword + b"=" + value + b"\n"
-
- # We use a hardcoded "././@PaxHeader" name like star does
- # instead of the one that POSIX recommends.
- info = {}
- info["name"] = "././@PaxHeader"
- info["type"] = type
- info["size"] = len(records)
- info["magic"] = POSIX_MAGIC
-
- # Create pax header + record blocks.
- return cls._create_header(info, USTAR_FORMAT, "ascii", "replace") + \
- cls._create_payload(records)
-
- @classmethod
- def frombuf(cls, buf, encoding, errors):
- """Construct a TarInfo object from a 512 byte bytes object.
- """
- if len(buf) == 0:
- raise EmptyHeaderError("empty header")
- if len(buf) != BLOCKSIZE:
- raise TruncatedHeaderError("truncated header")
- if buf.count(NUL) == BLOCKSIZE:
- raise EOFHeaderError("end of file header")
-
- chksum = nti(buf[148:156])
- if chksum not in calc_chksums(buf):
- raise InvalidHeaderError("bad checksum")
-
- obj = cls()
- obj.name = nts(buf[0:100], encoding, errors)
- obj.mode = nti(buf[100:108])
- obj.uid = nti(buf[108:116])
- obj.gid = nti(buf[116:124])
- obj.size = nti(buf[124:136])
- obj.mtime = nti(buf[136:148])
- obj.chksum = chksum
- obj.type = buf[156:157]
- obj.linkname = nts(buf[157:257], encoding, errors)
- obj.uname = nts(buf[265:297], encoding, errors)
- obj.gname = nts(buf[297:329], encoding, errors)
- obj.devmajor = nti(buf[329:337])
- obj.devminor = nti(buf[337:345])
- prefix = nts(buf[345:500], encoding, errors)
-
- # Old V7 tar format represents a directory as a regular
- # file with a trailing slash.
- if obj.type == AREGTYPE and obj.name.endswith("/"):
- obj.type = DIRTYPE
-
- # The old GNU sparse format occupies some of the unused
- # space in the buffer for up to 4 sparse structures.
- # Save the them for later processing in _proc_sparse().
- if obj.type == GNUTYPE_SPARSE:
- pos = 386
- structs = []
- for i in range(4):
- try:
- offset = nti(buf[pos:pos + 12])
- numbytes = nti(buf[pos + 12:pos + 24])
- except ValueError:
- break
- structs.append((offset, numbytes))
- pos += 24
- isextended = bool(buf[482])
- origsize = nti(buf[483:495])
- obj._sparse_structs = (structs, isextended, origsize)
-
- # Remove redundant slashes from directories.
- if obj.isdir():
- obj.name = obj.name.rstrip("/")
-
- # Reconstruct a ustar longname.
- if prefix and obj.type not in GNU_TYPES:
- obj.name = prefix + "/" + obj.name
- return obj
-
- @classmethod
- def fromtarfile(cls, tarfile):
- """Return the next TarInfo object from TarFile object
- tarfile.
- """
- buf = tarfile.fileobj.read(BLOCKSIZE)
- obj = cls.frombuf(buf, tarfile.encoding, tarfile.errors)
- obj.offset = tarfile.fileobj.tell() - BLOCKSIZE
- return obj._proc_member(tarfile)
-
- #--------------------------------------------------------------------------
- # The following are methods that are called depending on the type of a
- # member. The entry point is _proc_member() which can be overridden in a
- # subclass to add custom _proc_*() methods. A _proc_*() method MUST
- # implement the following
- # operations:
- # 1. Set self.offset_data to the position where the data blocks begin,
- # if there is data that follows.
- # 2. Set tarfile.offset to the position where the next member's header will
- # begin.
- # 3. Return self or another valid TarInfo object.
- def _proc_member(self, tarfile):
- """Choose the right processing method depending on
- the type and call it.
- """
- if self.type in (GNUTYPE_LONGNAME, GNUTYPE_LONGLINK):
- return self._proc_gnulong(tarfile)
- elif self.type == GNUTYPE_SPARSE:
- return self._proc_sparse(tarfile)
- elif self.type in (XHDTYPE, XGLTYPE, SOLARIS_XHDTYPE):
- return self._proc_pax(tarfile)
- else:
- return self._proc_builtin(tarfile)
-
- def _proc_builtin(self, tarfile):
- """Process a builtin type or an unknown type which
- will be treated as a regular file.
- """
- self.offset_data = tarfile.fileobj.tell()
- offset = self.offset_data
- if self.isreg() or self.type not in SUPPORTED_TYPES:
- # Skip the following data blocks.
- offset += self._block(self.size)
- tarfile.offset = offset
-
- # Patch the TarInfo object with saved global
- # header information.
- self._apply_pax_info(tarfile.pax_headers, tarfile.encoding, tarfile.errors)
-
- return self
-
- def _proc_gnulong(self, tarfile):
- """Process the blocks that hold a GNU longname
- or longlink member.
- """
- buf = tarfile.fileobj.read(self._block(self.size))
-
- # Fetch the next header and process it.
- try:
- next = self.fromtarfile(tarfile)
- except HeaderError:
- raise SubsequentHeaderError("missing or bad subsequent header")
-
- # Patch the TarInfo object from the next header with
- # the longname information.
- next.offset = self.offset
- if self.type == GNUTYPE_LONGNAME:
- next.name = nts(buf, tarfile.encoding, tarfile.errors)
- elif self.type == GNUTYPE_LONGLINK:
- next.linkname = nts(buf, tarfile.encoding, tarfile.errors)
-
- return next
-
- def _proc_sparse(self, tarfile):
- """Process a GNU sparse header plus extra headers.
- """
- # We already collected some sparse structures in frombuf().
- structs, isextended, origsize = self._sparse_structs
- del self._sparse_structs
-
- # Collect sparse structures from extended header blocks.
- while isextended:
- buf = tarfile.fileobj.read(BLOCKSIZE)
- pos = 0
- for i in range(21):
- try:
- offset = nti(buf[pos:pos + 12])
- numbytes = nti(buf[pos + 12:pos + 24])
- except ValueError:
- break
- if offset and numbytes:
- structs.append((offset, numbytes))
- pos += 24
- isextended = bool(buf[504])
- self.sparse = structs
-
- self.offset_data = tarfile.fileobj.tell()
- tarfile.offset = self.offset_data + self._block(self.size)
- self.size = origsize
- return self
-
- def _proc_pax(self, tarfile):
- """Process an extended or global header as described in
- POSIX.1-2008.
- """
- # Read the header information.
- buf = tarfile.fileobj.read(self._block(self.size))
-
- # A pax header stores supplemental information for either
- # the following file (extended) or all following files
- # (global).
- if self.type == XGLTYPE:
- pax_headers = tarfile.pax_headers
- else:
- pax_headers = tarfile.pax_headers.copy()
-
- # Check if the pax header contains a hdrcharset field. This tells us
- # the encoding of the path, linkpath, uname and gname fields. Normally,
- # these fields are UTF-8 encoded but since POSIX.1-2008 tar
- # implementations are allowed to store them as raw binary strings if
- # the translation to UTF-8 fails.
- match = re.search(br"\d+ hdrcharset=([^\n]+)\n", buf)
- if match is not None:
- pax_headers["hdrcharset"] = match.group(1).decode("utf8")
-
- # For the time being, we don't care about anything other than "BINARY".
- # The only other value that is currently allowed by the standard is
- # "ISO-IR 10646 2000 UTF-8" in other words UTF-8.
- hdrcharset = pax_headers.get("hdrcharset")
- if hdrcharset == "BINARY":
- encoding = tarfile.encoding
- else:
- encoding = "utf8"
-
- # Parse pax header information. A record looks like that:
- # "%d %s=%s\n" % (length, keyword, value). length is the size
- # of the complete record including the length field itself and
- # the newline. keyword and value are both UTF-8 encoded strings.
- regex = re.compile(br"(\d+) ([^=]+)=")
- pos = 0
- while True:
- match = regex.match(buf, pos)
- if not match:
- break
-
- length, keyword = match.groups()
- length = int(length)
- value = buf[match.end(2) + 1:match.start(1) + length - 1]
-
- # Normally, we could just use "utf8" as the encoding and "strict"
- # as the error handler, but we better not take the risk. For
- # example, GNU tar <= 1.23 is known to store filenames it cannot
- # translate to UTF-8 as raw strings (unfortunately without a
- # hdrcharset=BINARY header).
- # We first try the strict standard encoding, and if that fails we
- # fall back on the user's encoding and error handler.
- keyword = self._decode_pax_field(keyword, "utf8", "utf8",
- tarfile.errors)
- if keyword in PAX_NAME_FIELDS:
- value = self._decode_pax_field(value, encoding, tarfile.encoding,
- tarfile.errors)
- else:
- value = self._decode_pax_field(value, "utf8", "utf8",
- tarfile.errors)
-
- pax_headers[keyword] = value
- pos += length
-
- # Fetch the next header.
- try:
- next = self.fromtarfile(tarfile)
- except HeaderError:
- raise SubsequentHeaderError("missing or bad subsequent header")
-
- # Process GNU sparse information.
- if "GNU.sparse.map" in pax_headers:
- # GNU extended sparse format version 0.1.
- self._proc_gnusparse_01(next, pax_headers)
-
- elif "GNU.sparse.size" in pax_headers:
- # GNU extended sparse format version 0.0.
- self._proc_gnusparse_00(next, pax_headers, buf)
-
- elif pax_headers.get("GNU.sparse.major") == "1" and pax_headers.get("GNU.sparse.minor") == "0":
- # GNU extended sparse format version 1.0.
- self._proc_gnusparse_10(next, pax_headers, tarfile)
-
- if self.type in (XHDTYPE, SOLARIS_XHDTYPE):
- # Patch the TarInfo object with the extended header info.
- next._apply_pax_info(pax_headers, tarfile.encoding, tarfile.errors)
- next.offset = self.offset
-
- if "size" in pax_headers:
- # If the extended header replaces the size field,
- # we need to recalculate the offset where the next
- # header starts.
- offset = next.offset_data
- if next.isreg() or next.type not in SUPPORTED_TYPES:
- offset += next._block(next.size)
- tarfile.offset = offset
-
- return next
-
- def _proc_gnusparse_00(self, next, pax_headers, buf):
- """Process a GNU tar extended sparse header, version 0.0.
- """
- offsets = []
- for match in re.finditer(br"\d+ GNU.sparse.offset=(\d+)\n", buf):
- offsets.append(int(match.group(1)))
- numbytes = []
- for match in re.finditer(br"\d+ GNU.sparse.numbytes=(\d+)\n", buf):
- numbytes.append(int(match.group(1)))
- next.sparse = list(zip(offsets, numbytes))
-
- def _proc_gnusparse_01(self, next, pax_headers):
- """Process a GNU tar extended sparse header, version 0.1.
- """
- sparse = [int(x) for x in pax_headers["GNU.sparse.map"].split(",")]
- next.sparse = list(zip(sparse[::2], sparse[1::2]))
-
- def _proc_gnusparse_10(self, next, pax_headers, tarfile):
- """Process a GNU tar extended sparse header, version 1.0.
- """
- fields = None
- sparse = []
- buf = tarfile.fileobj.read(BLOCKSIZE)
- fields, buf = buf.split(b"\n", 1)
- fields = int(fields)
- while len(sparse) < fields * 2:
- if b"\n" not in buf:
- buf += tarfile.fileobj.read(BLOCKSIZE)
- number, buf = buf.split(b"\n", 1)
- sparse.append(int(number))
- next.offset_data = tarfile.fileobj.tell()
- next.sparse = list(zip(sparse[::2], sparse[1::2]))
-
- def _apply_pax_info(self, pax_headers, encoding, errors):
- """Replace fields with supplemental information from a previous
- pax extended or global header.
- """
- for keyword, value in pax_headers.items():
- if keyword == "GNU.sparse.name":
- setattr(self, "path", value)
- elif keyword == "GNU.sparse.size":
- setattr(self, "size", int(value))
- elif keyword == "GNU.sparse.realsize":
- setattr(self, "size", int(value))
- elif keyword in PAX_FIELDS:
- if keyword in PAX_NUMBER_FIELDS:
- try:
- value = PAX_NUMBER_FIELDS[keyword](value)
- except ValueError:
- value = 0
- if keyword == "path":
- value = value.rstrip("/")
- setattr(self, keyword, value)
-
- self.pax_headers = pax_headers.copy()
-
- def _decode_pax_field(self, value, encoding, fallback_encoding, fallback_errors):
- """Decode a single field from a pax record.
- """
- try:
- return value.decode(encoding, "strict")
- except UnicodeDecodeError:
- return value.decode(fallback_encoding, fallback_errors)
-
- def _block(self, count):
- """Round up a byte count by BLOCKSIZE and return it,
- e.g. _block(834) => 1024.
- """
- blocks, remainder = divmod(count, BLOCKSIZE)
- if remainder:
- blocks += 1
- return blocks * BLOCKSIZE
-
- def isreg(self):
- return self.type in REGULAR_TYPES
- def isfile(self):
- return self.isreg()
- def isdir(self):
- return self.type == DIRTYPE
- def issym(self):
- return self.type == SYMTYPE
- def islnk(self):
- return self.type == LNKTYPE
- def ischr(self):
- return self.type == CHRTYPE
- def isblk(self):
- return self.type == BLKTYPE
- def isfifo(self):
- return self.type == FIFOTYPE
- def issparse(self):
- return self.sparse is not None
- def isdev(self):
- return self.type in (CHRTYPE, BLKTYPE, FIFOTYPE)
-# class TarInfo
-
-class TarFile(object):
- """The TarFile Class provides an interface to tar archives.
- """
-
- debug = 0 # May be set from 0 (no msgs) to 3 (all msgs)
-
- dereference = False # If true, add content of linked file to the
- # tar file, else the link.
-
- ignore_zeros = False # If true, skips empty or invalid blocks and
- # continues processing.
-
- errorlevel = 1 # If 0, fatal errors only appear in debug
- # messages (if debug >= 0). If > 0, errors
- # are passed to the caller as exceptions.
-
- format = DEFAULT_FORMAT # The format to use when creating an archive.
-
- encoding = ENCODING # Encoding for 8-bit character strings.
-
- errors = None # Error handler for unicode conversion.
-
- tarinfo = TarInfo # The default TarInfo class to use.
-
- fileobject = ExFileObject # The default ExFileObject class to use.
-
- def __init__(self, name=None, mode="r", fileobj=None, format=None,
- tarinfo=None, dereference=None, ignore_zeros=None, encoding=None,
- errors="surrogateescape", pax_headers=None, debug=None, errorlevel=None):
- """Open an (uncompressed) tar archive `name'. `mode' is either 'r' to
- read from an existing archive, 'a' to append data to an existing
- file or 'w' to create a new file overwriting an existing one. `mode'
- defaults to 'r'.
- If `fileobj' is given, it is used for reading or writing data. If it
- can be determined, `mode' is overridden by `fileobj's mode.
- `fileobj' is not closed, when TarFile is closed.
- """
- if len(mode) > 1 or mode not in "raw":
- raise ValueError("mode must be 'r', 'a' or 'w'")
- self.mode = mode
- self._mode = {"r": "rb", "a": "r+b", "w": "wb"}[mode]
-
- if not fileobj:
- if self.mode == "a" and not os.path.exists(name):
- # Create nonexistent files in append mode.
- self.mode = "w"
- self._mode = "wb"
- fileobj = bltn_open(name, self._mode)
- self._extfileobj = False
- else:
- if name is None and hasattr(fileobj, "name"):
- name = fileobj.name
- if hasattr(fileobj, "mode"):
- self._mode = fileobj.mode
- self._extfileobj = True
- self.name = os.path.abspath(name) if name else None
- self.fileobj = fileobj
-
- # Init attributes.
- if format is not None:
- self.format = format
- if tarinfo is not None:
- self.tarinfo = tarinfo
- if dereference is not None:
- self.dereference = dereference
- if ignore_zeros is not None:
- self.ignore_zeros = ignore_zeros
- if encoding is not None:
- self.encoding = encoding
- self.errors = errors
-
- if pax_headers is not None and self.format == PAX_FORMAT:
- self.pax_headers = pax_headers
- else:
- self.pax_headers = {}
-
- if debug is not None:
- self.debug = debug
- if errorlevel is not None:
- self.errorlevel = errorlevel
-
- # Init datastructures.
- self.closed = False
- self.members = [] # list of members as TarInfo objects
- self._loaded = False # flag if all members have been read
- self.offset = self.fileobj.tell()
- # current position in the archive file
- self.inodes = {} # dictionary caching the inodes of
- # archive members already added
-
- try:
- if self.mode == "r":
- self.firstmember = None
- self.firstmember = self.next()
-
- if self.mode == "a":
- # Move to the end of the archive,
- # before the first empty block.
- while True:
- self.fileobj.seek(self.offset)
- try:
- tarinfo = self.tarinfo.fromtarfile(self)
- self.members.append(tarinfo)
- except EOFHeaderError:
- self.fileobj.seek(self.offset)
- break
- except HeaderError as e:
- raise ReadError(str(e))
-
- if self.mode in "aw":
- self._loaded = True
-
- if self.pax_headers:
- buf = self.tarinfo.create_pax_global_header(self.pax_headers.copy())
- self.fileobj.write(buf)
- self.offset += len(buf)
- except:
- if not self._extfileobj:
- self.fileobj.close()
- self.closed = True
- raise
-
- #--------------------------------------------------------------------------
- # Below are the classmethods which act as alternate constructors to the
- # TarFile class. The open() method is the only one that is needed for
- # public use; it is the "super"-constructor and is able to select an
- # adequate "sub"-constructor for a particular compression using the mapping
- # from OPEN_METH.
- #
- # This concept allows one to subclass TarFile without losing the comfort of
- # the super-constructor. A sub-constructor is registered and made available
- # by adding it to the mapping in OPEN_METH.
-
- @classmethod
- def open(cls, name=None, mode="r", fileobj=None, bufsize=RECORDSIZE, **kwargs):
- """Open a tar archive for reading, writing or appending. Return
- an appropriate TarFile class.
-
- mode:
- 'r' or 'r:*' open for reading with transparent compression
- 'r:' open for reading exclusively uncompressed
- 'r:gz' open for reading with gzip compression
- 'r:bz2' open for reading with bzip2 compression
- 'a' or 'a:' open for appending, creating the file if necessary
- 'w' or 'w:' open for writing without compression
- 'w:gz' open for writing with gzip compression
- 'w:bz2' open for writing with bzip2 compression
-
- 'r|*' open a stream of tar blocks with transparent compression
- 'r|' open an uncompressed stream of tar blocks for reading
- 'r|gz' open a gzip compressed stream of tar blocks
- 'r|bz2' open a bzip2 compressed stream of tar blocks
- 'w|' open an uncompressed stream for writing
- 'w|gz' open a gzip compressed stream for writing
- 'w|bz2' open a bzip2 compressed stream for writing
- """
-
- if not name and not fileobj:
- raise ValueError("nothing to open")
-
- if mode in ("r", "r:*"):
- # Find out which *open() is appropriate for opening the file.
- for comptype in cls.OPEN_METH:
- func = getattr(cls, cls.OPEN_METH[comptype])
- if fileobj is not None:
- saved_pos = fileobj.tell()
- try:
- return func(name, "r", fileobj, **kwargs)
- except (ReadError, CompressionError) as e:
- if fileobj is not None:
- fileobj.seek(saved_pos)
- continue
- raise ReadError("file could not be opened successfully")
-
- elif ":" in mode:
- filemode, comptype = mode.split(":", 1)
- filemode = filemode or "r"
- comptype = comptype or "tar"
-
- # Select the *open() function according to
- # given compression.
- if comptype in cls.OPEN_METH:
- func = getattr(cls, cls.OPEN_METH[comptype])
- else:
- raise CompressionError("unknown compression type %r" % comptype)
- return func(name, filemode, fileobj, **kwargs)
-
- elif "|" in mode:
- filemode, comptype = mode.split("|", 1)
- filemode = filemode or "r"
- comptype = comptype or "tar"
-
- if filemode not in "rw":
- raise ValueError("mode must be 'r' or 'w'")
-
- stream = _Stream(name, filemode, comptype, fileobj, bufsize)
- try:
- t = cls(name, filemode, stream, **kwargs)
- except:
- stream.close()
- raise
- t._extfileobj = False
- return t
-
- elif mode in "aw":
- return cls.taropen(name, mode, fileobj, **kwargs)
-
- raise ValueError("undiscernible mode")
-
- @classmethod
- def taropen(cls, name, mode="r", fileobj=None, **kwargs):
- """Open uncompressed tar archive name for reading or writing.
- """
- if len(mode) > 1 or mode not in "raw":
- raise ValueError("mode must be 'r', 'a' or 'w'")
- return cls(name, mode, fileobj, **kwargs)
-
- @classmethod
- def gzopen(cls, name, mode="r", fileobj=None, compresslevel=9, **kwargs):
- """Open gzip compressed tar archive name for reading or writing.
- Appending is not allowed.
- """
- if len(mode) > 1 or mode not in "rw":
- raise ValueError("mode must be 'r' or 'w'")
-
- try:
- import gzip
- gzip.GzipFile
- except (ImportError, AttributeError):
- raise CompressionError("gzip module is not available")
-
- extfileobj = fileobj is not None
- try:
- fileobj = gzip.GzipFile(name, mode + "b", compresslevel, fileobj)
- t = cls.taropen(name, mode, fileobj, **kwargs)
- except IOError:
- if not extfileobj and fileobj is not None:
- fileobj.close()
- if fileobj is None:
- raise
- raise ReadError("not a gzip file")
- except:
- if not extfileobj and fileobj is not None:
- fileobj.close()
- raise
- t._extfileobj = extfileobj
- return t
-
- @classmethod
- def bz2open(cls, name, mode="r", fileobj=None, compresslevel=9, **kwargs):
- """Open bzip2 compressed tar archive name for reading or writing.
- Appending is not allowed.
- """
- if len(mode) > 1 or mode not in "rw":
- raise ValueError("mode must be 'r' or 'w'.")
-
- try:
- import bz2
- except ImportError:
- raise CompressionError("bz2 module is not available")
-
- if fileobj is not None:
- fileobj = _BZ2Proxy(fileobj, mode)
- else:
- fileobj = bz2.BZ2File(name, mode, compresslevel=compresslevel)
-
- try:
- t = cls.taropen(name, mode, fileobj, **kwargs)
- except (IOError, EOFError):
- fileobj.close()
- raise ReadError("not a bzip2 file")
- t._extfileobj = False
- return t
-
- # All *open() methods are registered here.
- OPEN_METH = {
- "tar": "taropen", # uncompressed tar
- "gz": "gzopen", # gzip compressed tar
- "bz2": "bz2open" # bzip2 compressed tar
- }
-
- #--------------------------------------------------------------------------
- # The public methods which TarFile provides:
-
- def close(self):
- """Close the TarFile. In write-mode, two finishing zero blocks are
- appended to the archive.
- """
- if self.closed:
- return
-
- if self.mode in "aw":
- self.fileobj.write(NUL * (BLOCKSIZE * 2))
- self.offset += (BLOCKSIZE * 2)
- # fill up the end with zero-blocks
- # (like option -b20 for tar does)
- blocks, remainder = divmod(self.offset, RECORDSIZE)
- if remainder > 0:
- self.fileobj.write(NUL * (RECORDSIZE - remainder))
-
- if not self._extfileobj:
- self.fileobj.close()
- self.closed = True
-
- def getmember(self, name):
- """Return a TarInfo object for member `name'. If `name' can not be
- found in the archive, KeyError is raised. If a member occurs more
- than once in the archive, its last occurrence is assumed to be the
- most up-to-date version.
- """
- tarinfo = self._getmember(name)
- if tarinfo is None:
- raise KeyError("filename %r not found" % name)
- return tarinfo
-
- def getmembers(self):
- """Return the members of the archive as a list of TarInfo objects. The
- list has the same order as the members in the archive.
- """
- self._check()
- if not self._loaded: # if we want to obtain a list of
- self._load() # all members, we first have to
- # scan the whole archive.
- return self.members
-
- def getnames(self):
- """Return the members of the archive as a list of their names. It has
- the same order as the list returned by getmembers().
- """
- return [tarinfo.name for tarinfo in self.getmembers()]
-
- def gettarinfo(self, name=None, arcname=None, fileobj=None):
- """Create a TarInfo object for either the file `name' or the file
- object `fileobj' (using os.fstat on its file descriptor). You can
- modify some of the TarInfo's attributes before you add it using
- addfile(). If given, `arcname' specifies an alternative name for the
- file in the archive.
- """
- self._check("aw")
-
- # When fileobj is given, replace name by
- # fileobj's real name.
- if fileobj is not None:
- name = fileobj.name
-
- # Building the name of the member in the archive.
- # Backward slashes are converted to forward slashes,
- # Absolute paths are turned to relative paths.
- if arcname is None:
- arcname = name
- drv, arcname = os.path.splitdrive(arcname)
- arcname = arcname.replace(os.sep, "/")
- arcname = arcname.lstrip("/")
-
- # Now, fill the TarInfo object with
- # information specific for the file.
- tarinfo = self.tarinfo()
- tarinfo.tarfile = self
-
- # Use os.stat or os.lstat, depending on platform
- # and if symlinks shall be resolved.
- if fileobj is None:
- if hasattr(os, "lstat") and not self.dereference:
- statres = os.lstat(name)
- else:
- statres = os.stat(name)
- else:
- statres = os.fstat(fileobj.fileno())
- linkname = ""
-
- stmd = statres.st_mode
- if stat.S_ISREG(stmd):
- inode = (statres.st_ino, statres.st_dev)
- if not self.dereference and statres.st_nlink > 1 and \
- inode in self.inodes and arcname != self.inodes[inode]:
- # Is it a hardlink to an already
- # archived file?
- type = LNKTYPE
- linkname = self.inodes[inode]
- else:
- # The inode is added only if its valid.
- # For win32 it is always 0.
- type = REGTYPE
- if inode[0]:
- self.inodes[inode] = arcname
- elif stat.S_ISDIR(stmd):
- type = DIRTYPE
- elif stat.S_ISFIFO(stmd):
- type = FIFOTYPE
- elif stat.S_ISLNK(stmd):
- type = SYMTYPE
- linkname = os.readlink(name)
- elif stat.S_ISCHR(stmd):
- type = CHRTYPE
- elif stat.S_ISBLK(stmd):
- type = BLKTYPE
- else:
- return None
-
- # Fill the TarInfo object with all
- # information we can get.
- tarinfo.name = arcname
- tarinfo.mode = stmd
- tarinfo.uid = statres.st_uid
- tarinfo.gid = statres.st_gid
- if type == REGTYPE:
- tarinfo.size = statres.st_size
- else:
- tarinfo.size = 0
- tarinfo.mtime = statres.st_mtime
- tarinfo.type = type
- tarinfo.linkname = linkname
- if pwd:
- try:
- tarinfo.uname = pwd.getpwuid(tarinfo.uid)[0]
- except KeyError:
- pass
- if grp:
- try:
- tarinfo.gname = grp.getgrgid(tarinfo.gid)[0]
- except KeyError:
- pass
-
- if type in (CHRTYPE, BLKTYPE):
- if hasattr(os, "major") and hasattr(os, "minor"):
- tarinfo.devmajor = os.major(statres.st_rdev)
- tarinfo.devminor = os.minor(statres.st_rdev)
- return tarinfo
-
- def list(self, verbose=True):
- """Print a table of contents to sys.stdout. If `verbose' is False, only
- the names of the members are printed. If it is True, an `ls -l'-like
- output is produced.
- """
- self._check()
-
- for tarinfo in self:
- if verbose:
- print(filemode(tarinfo.mode), end=' ')
- print("%s/%s" % (tarinfo.uname or tarinfo.uid,
- tarinfo.gname or tarinfo.gid), end=' ')
- if tarinfo.ischr() or tarinfo.isblk():
- print("%10s" % ("%d,%d" \
- % (tarinfo.devmajor, tarinfo.devminor)), end=' ')
- else:
- print("%10d" % tarinfo.size, end=' ')
- print("%d-%02d-%02d %02d:%02d:%02d" \
- % time.localtime(tarinfo.mtime)[:6], end=' ')
-
- print(tarinfo.name + ("/" if tarinfo.isdir() else ""), end=' ')
-
- if verbose:
- if tarinfo.issym():
- print("->", tarinfo.linkname, end=' ')
- if tarinfo.islnk():
- print("link to", tarinfo.linkname, end=' ')
- print()
-
- def add(self, name, arcname=None, recursive=True, exclude=None, filter=None):
- """Add the file `name' to the archive. `name' may be any type of file
- (directory, fifo, symbolic link, etc.). If given, `arcname'
- specifies an alternative name for the file in the archive.
- Directories are added recursively by default. This can be avoided by
- setting `recursive' to False. `exclude' is a function that should
- return True for each filename to be excluded. `filter' is a function
- that expects a TarInfo object argument and returns the changed
- TarInfo object, if it returns None the TarInfo object will be
- excluded from the archive.
- """
- self._check("aw")
-
- if arcname is None:
- arcname = name
-
- # Exclude pathnames.
- if exclude is not None:
- import warnings
- warnings.warn("use the filter argument instead",
- DeprecationWarning, 2)
- if exclude(name):
- self._dbg(2, "tarfile: Excluded %r" % name)
- return
-
- # Skip if somebody tries to archive the archive...
- if self.name is not None and os.path.abspath(name) == self.name:
- self._dbg(2, "tarfile: Skipped %r" % name)
- return
-
- self._dbg(1, name)
-
- # Create a TarInfo object from the file.
- tarinfo = self.gettarinfo(name, arcname)
-
- if tarinfo is None:
- self._dbg(1, "tarfile: Unsupported type %r" % name)
- return
-
- # Change or exclude the TarInfo object.
- if filter is not None:
- tarinfo = filter(tarinfo)
- if tarinfo is None:
- self._dbg(2, "tarfile: Excluded %r" % name)
- return
-
- # Append the tar header and data to the archive.
- if tarinfo.isreg():
- f = bltn_open(name, "rb")
- self.addfile(tarinfo, f)
- f.close()
-
- elif tarinfo.isdir():
- self.addfile(tarinfo)
- if recursive:
- for f in os.listdir(name):
- self.add(os.path.join(name, f), os.path.join(arcname, f),
- recursive, exclude, filter=filter)
-
- else:
- self.addfile(tarinfo)
-
- def addfile(self, tarinfo, fileobj=None):
- """Add the TarInfo object `tarinfo' to the archive. If `fileobj' is
- given, tarinfo.size bytes are read from it and added to the archive.
- You can create TarInfo objects using gettarinfo().
- On Windows platforms, `fileobj' should always be opened with mode
- 'rb' to avoid irritation about the file size.
- """
- self._check("aw")
-
- tarinfo = copy.copy(tarinfo)
-
- buf = tarinfo.tobuf(self.format, self.encoding, self.errors)
- self.fileobj.write(buf)
- self.offset += len(buf)
-
- # If there's data to follow, append it.
- if fileobj is not None:
- copyfileobj(fileobj, self.fileobj, tarinfo.size)
- blocks, remainder = divmod(tarinfo.size, BLOCKSIZE)
- if remainder > 0:
- self.fileobj.write(NUL * (BLOCKSIZE - remainder))
- blocks += 1
- self.offset += blocks * BLOCKSIZE
-
- self.members.append(tarinfo)
-
- def extractall(self, path=".", members=None):
- """Extract all members from the archive to the current working
- directory and set owner, modification time and permissions on
- directories afterwards. `path' specifies a different directory
- to extract to. `members' is optional and must be a subset of the
- list returned by getmembers().
- """
- directories = []
-
- if members is None:
- members = self
-
- for tarinfo in members:
- if tarinfo.isdir():
- # Extract directories with a safe mode.
- directories.append(tarinfo)
- tarinfo = copy.copy(tarinfo)
- tarinfo.mode = 0o700
- # Do not set_attrs directories, as we will do that further down
- self.extract(tarinfo, path, set_attrs=not tarinfo.isdir())
-
- # Reverse sort directories.
- directories.sort(key=lambda a: a.name)
- directories.reverse()
-
- # Set correct owner, mtime and filemode on directories.
- for tarinfo in directories:
- dirpath = os.path.join(path, tarinfo.name)
- try:
- self.chown(tarinfo, dirpath)
- self.utime(tarinfo, dirpath)
- self.chmod(tarinfo, dirpath)
- except ExtractError as e:
- if self.errorlevel > 1:
- raise
- else:
- self._dbg(1, "tarfile: %s" % e)
-
- def extract(self, member, path="", set_attrs=True):
- """Extract a member from the archive to the current working directory,
- using its full name. Its file information is extracted as accurately
- as possible. `member' may be a filename or a TarInfo object. You can
- specify a different directory using `path'. File attributes (owner,
- mtime, mode) are set unless `set_attrs' is False.
- """
- self._check("r")
-
- if isinstance(member, str):
- tarinfo = self.getmember(member)
- else:
- tarinfo = member
-
- # Prepare the link target for makelink().
- if tarinfo.islnk():
- tarinfo._link_target = os.path.join(path, tarinfo.linkname)
-
- try:
- self._extract_member(tarinfo, os.path.join(path, tarinfo.name),
- set_attrs=set_attrs)
- except EnvironmentError as e:
- if self.errorlevel > 0:
- raise
- else:
- if e.filename is None:
- self._dbg(1, "tarfile: %s" % e.strerror)
- else:
- self._dbg(1, "tarfile: %s %r" % (e.strerror, e.filename))
- except ExtractError as e:
- if self.errorlevel > 1:
- raise
- else:
- self._dbg(1, "tarfile: %s" % e)
-
- def extractfile(self, member):
- """Extract a member from the archive as a file object. `member' may be
- a filename or a TarInfo object. If `member' is a regular file, a
- file-like object is returned. If `member' is a link, a file-like
- object is constructed from the link's target. If `member' is none of
- the above, None is returned.
- The file-like object is read-only and provides the following
- methods: read(), readline(), readlines(), seek() and tell()
- """
- self._check("r")
-
- if isinstance(member, str):
- tarinfo = self.getmember(member)
- else:
- tarinfo = member
-
- if tarinfo.isreg():
- return self.fileobject(self, tarinfo)
-
- elif tarinfo.type not in SUPPORTED_TYPES:
- # If a member's type is unknown, it is treated as a
- # regular file.
- return self.fileobject(self, tarinfo)
-
- elif tarinfo.islnk() or tarinfo.issym():
- if isinstance(self.fileobj, _Stream):
- # A small but ugly workaround for the case that someone tries
- # to extract a (sym)link as a file-object from a non-seekable
- # stream of tar blocks.
- raise StreamError("cannot extract (sym)link as file object")
- else:
- # A (sym)link's file object is its target's file object.
- return self.extractfile(self._find_link_target(tarinfo))
- else:
- # If there's no data associated with the member (directory, chrdev,
- # blkdev, etc.), return None instead of a file object.
- return None
-
- def _extract_member(self, tarinfo, targetpath, set_attrs=True):
- """Extract the TarInfo object tarinfo to a physical
- file called targetpath.
- """
- # Fetch the TarInfo object for the given name
- # and build the destination pathname, replacing
- # forward slashes to platform specific separators.
- targetpath = targetpath.rstrip("/")
- targetpath = targetpath.replace("/", os.sep)
-
- # Create all upper directories.
- upperdirs = os.path.dirname(targetpath)
- if upperdirs and not os.path.exists(upperdirs):
- # Create directories that are not part of the archive with
- # default permissions.
- os.makedirs(upperdirs)
-
- if tarinfo.islnk() or tarinfo.issym():
- self._dbg(1, "%s -> %s" % (tarinfo.name, tarinfo.linkname))
- else:
- self._dbg(1, tarinfo.name)
-
- if tarinfo.isreg():
- self.makefile(tarinfo, targetpath)
- elif tarinfo.isdir():
- self.makedir(tarinfo, targetpath)
- elif tarinfo.isfifo():
- self.makefifo(tarinfo, targetpath)
- elif tarinfo.ischr() or tarinfo.isblk():
- self.makedev(tarinfo, targetpath)
- elif tarinfo.islnk() or tarinfo.issym():
- self.makelink(tarinfo, targetpath)
- elif tarinfo.type not in SUPPORTED_TYPES:
- self.makeunknown(tarinfo, targetpath)
- else:
- self.makefile(tarinfo, targetpath)
-
- if set_attrs:
- self.chown(tarinfo, targetpath)
- if not tarinfo.issym():
- self.chmod(tarinfo, targetpath)
- self.utime(tarinfo, targetpath)
-
- #--------------------------------------------------------------------------
- # Below are the different file methods. They are called via
- # _extract_member() when extract() is called. They can be replaced in a
- # subclass to implement other functionality.
-
- def makedir(self, tarinfo, targetpath):
- """Make a directory called targetpath.
- """
- try:
- # Use a safe mode for the directory, the real mode is set
- # later in _extract_member().
- os.mkdir(targetpath, 0o700)
- except EnvironmentError as e:
- if e.errno != errno.EEXIST:
- raise
-
- def makefile(self, tarinfo, targetpath):
- """Make a file called targetpath.
- """
- source = self.fileobj
- source.seek(tarinfo.offset_data)
- target = bltn_open(targetpath, "wb")
- if tarinfo.sparse is not None:
- for offset, size in tarinfo.sparse:
- target.seek(offset)
- copyfileobj(source, target, size)
- else:
- copyfileobj(source, target, tarinfo.size)
- target.seek(tarinfo.size)
- target.truncate()
- target.close()
-
- def makeunknown(self, tarinfo, targetpath):
- """Make a file from a TarInfo object with an unknown type
- at targetpath.
- """
- self.makefile(tarinfo, targetpath)
- self._dbg(1, "tarfile: Unknown file type %r, " \
- "extracted as regular file." % tarinfo.type)
-
- def makefifo(self, tarinfo, targetpath):
- """Make a fifo called targetpath.
- """
- if hasattr(os, "mkfifo"):
- os.mkfifo(targetpath)
- else:
- raise ExtractError("fifo not supported by system")
-
- def makedev(self, tarinfo, targetpath):
- """Make a character or block device called targetpath.
- """
- if not hasattr(os, "mknod") or not hasattr(os, "makedev"):
- raise ExtractError("special devices not supported by system")
-
- mode = tarinfo.mode
- if tarinfo.isblk():
- mode |= stat.S_IFBLK
- else:
- mode |= stat.S_IFCHR
-
- os.mknod(targetpath, mode,
- os.makedev(tarinfo.devmajor, tarinfo.devminor))
-
- def makelink(self, tarinfo, targetpath):
- """Make a (symbolic) link called targetpath. If it cannot be created
- (platform limitation), we try to make a copy of the referenced file
- instead of a link.
- """
- try:
- # For systems that support symbolic and hard links.
- if tarinfo.issym():
- os.symlink(tarinfo.linkname, targetpath)
- else:
- # See extract().
- if os.path.exists(tarinfo._link_target):
- os.link(tarinfo._link_target, targetpath)
- else:
- self._extract_member(self._find_link_target(tarinfo),
- targetpath)
- except symlink_exception:
- if tarinfo.issym():
- linkpath = os.path.join(os.path.dirname(tarinfo.name),
- tarinfo.linkname)
- else:
- linkpath = tarinfo.linkname
- else:
- try:
- self._extract_member(self._find_link_target(tarinfo),
- targetpath)
- except KeyError:
- raise ExtractError("unable to resolve link inside archive")
-
- def chown(self, tarinfo, targetpath):
- """Set owner of targetpath according to tarinfo.
- """
- if pwd and hasattr(os, "geteuid") and os.geteuid() == 0:
- # We have to be root to do so.
- try:
- g = grp.getgrnam(tarinfo.gname)[2]
- except KeyError:
- g = tarinfo.gid
- try:
- u = pwd.getpwnam(tarinfo.uname)[2]
- except KeyError:
- u = tarinfo.uid
- try:
- if tarinfo.issym() and hasattr(os, "lchown"):
- os.lchown(targetpath, u, g)
- else:
- if sys.platform != "os2emx":
- os.chown(targetpath, u, g)
- except EnvironmentError as e:
- raise ExtractError("could not change owner")
-
- def chmod(self, tarinfo, targetpath):
- """Set file permissions of targetpath according to tarinfo.
- """
- if hasattr(os, 'chmod'):
- try:
- os.chmod(targetpath, tarinfo.mode)
- except EnvironmentError as e:
- raise ExtractError("could not change mode")
-
- def utime(self, tarinfo, targetpath):
- """Set modification time of targetpath according to tarinfo.
- """
- if not hasattr(os, 'utime'):
- return
- try:
- os.utime(targetpath, (tarinfo.mtime, tarinfo.mtime))
- except EnvironmentError as e:
- raise ExtractError("could not change modification time")
-
- #--------------------------------------------------------------------------
- def next(self):
- """Return the next member of the archive as a TarInfo object, when
- TarFile is opened for reading. Return None if there is no more
- available.
- """
- self._check("ra")
- if self.firstmember is not None:
- m = self.firstmember
- self.firstmember = None
- return m
-
- # Read the next block.
- self.fileobj.seek(self.offset)
- tarinfo = None
- while True:
- try:
- tarinfo = self.tarinfo.fromtarfile(self)
- except EOFHeaderError as e:
- if self.ignore_zeros:
- self._dbg(2, "0x%X: %s" % (self.offset, e))
- self.offset += BLOCKSIZE
- continue
- except InvalidHeaderError as e:
- if self.ignore_zeros:
- self._dbg(2, "0x%X: %s" % (self.offset, e))
- self.offset += BLOCKSIZE
- continue
- elif self.offset == 0:
- raise ReadError(str(e))
- except EmptyHeaderError:
- if self.offset == 0:
- raise ReadError("empty file")
- except TruncatedHeaderError as e:
- if self.offset == 0:
- raise ReadError(str(e))
- except SubsequentHeaderError as e:
- raise ReadError(str(e))
- break
-
- if tarinfo is not None:
- self.members.append(tarinfo)
- else:
- self._loaded = True
-
- return tarinfo
-
- #--------------------------------------------------------------------------
- # Little helper methods:
-
- def _getmember(self, name, tarinfo=None, normalize=False):
- """Find an archive member by name from bottom to top.
- If tarinfo is given, it is used as the starting point.
- """
- # Ensure that all members have been loaded.
- members = self.getmembers()
-
- # Limit the member search list up to tarinfo.
- if tarinfo is not None:
- members = members[:members.index(tarinfo)]
-
- if normalize:
- name = os.path.normpath(name)
-
- for member in reversed(members):
- if normalize:
- member_name = os.path.normpath(member.name)
- else:
- member_name = member.name
-
- if name == member_name:
- return member
-
- def _load(self):
- """Read through the entire archive file and look for readable
- members.
- """
- while True:
- tarinfo = self.next()
- if tarinfo is None:
- break
- self._loaded = True
-
- def _check(self, mode=None):
- """Check if TarFile is still open, and if the operation's mode
- corresponds to TarFile's mode.
- """
- if self.closed:
- raise IOError("%s is closed" % self.__class__.__name__)
- if mode is not None and self.mode not in mode:
- raise IOError("bad operation for mode %r" % self.mode)
-
- def _find_link_target(self, tarinfo):
- """Find the target member of a symlink or hardlink member in the
- archive.
- """
- if tarinfo.issym():
- # Always search the entire archive.
- linkname = os.path.dirname(tarinfo.name) + "/" + tarinfo.linkname
- limit = None
- else:
- # Search the archive before the link, because a hard link is
- # just a reference to an already archived file.
- linkname = tarinfo.linkname
- limit = tarinfo
-
- member = self._getmember(linkname, tarinfo=limit, normalize=True)
- if member is None:
- raise KeyError("linkname %r not found" % linkname)
- return member
-
- def __iter__(self):
- """Provide an iterator object.
- """
- if self._loaded:
- return iter(self.members)
- else:
- return TarIter(self)
-
- def _dbg(self, level, msg):
- """Write debugging output to sys.stderr.
- """
- if level <= self.debug:
- print(msg, file=sys.stderr)
-
- def __enter__(self):
- self._check()
- return self
-
- def __exit__(self, type, value, traceback):
- if type is None:
- self.close()
- else:
- # An exception occurred. We must not call close() because
- # it would try to write end-of-archive blocks and padding.
- if not self._extfileobj:
- self.fileobj.close()
- self.closed = True
-# class TarFile
-
-class TarIter(object):
- """Iterator Class.
-
- for tarinfo in TarFile(...):
- suite...
- """
-
- def __init__(self, tarfile):
- """Construct a TarIter object.
- """
- self.tarfile = tarfile
- self.index = 0
- def __iter__(self):
- """Return iterator object.
- """
- return self
-
- def __next__(self):
- """Return the next item using TarFile's next() method.
- When all members have been read, set TarFile as _loaded.
- """
- # Fix for SF #1100429: Under rare circumstances it can
- # happen that getmembers() is called during iteration,
- # which will cause TarIter to stop prematurely.
- if not self.tarfile._loaded:
- tarinfo = self.tarfile.next()
- if not tarinfo:
- self.tarfile._loaded = True
- raise StopIteration
- else:
- try:
- tarinfo = self.tarfile.members[self.index]
- except IndexError:
- raise StopIteration
- self.index += 1
- return tarinfo
-
- next = __next__ # for Python 2.x
-
-#--------------------
-# exported functions
-#--------------------
-def is_tarfile(name):
- """Return True if name points to a tar archive that we
- are able to handle, else return False.
- """
- try:
- t = open(name)
- t.close()
- return True
- except TarError:
- return False
-
-bltn_open = open
-open = TarFile.open
diff --git a/src/pip/_vendor/distlib/compat.py b/src/pip/_vendor/distlib/compat.py
index e59410695..1fe3d225a 100644
--- a/src/pip/_vendor/distlib/compat.py
+++ b/src/pip/_vendor/distlib/compat.py
@@ -22,7 +22,6 @@ if sys.version_info[0] < 3: # pragma: no cover
from types import FileType as file_type
import __builtin__ as builtins
import ConfigParser as configparser
- from ._backport import shutil
from urlparse import urlparse, urlunparse, urljoin, urlsplit, urlunsplit
from urllib import (urlretrieve, quote as _quote, unquote, url2pathname,
pathname2url, ContentTooShortError, splittype)
@@ -313,10 +312,8 @@ except ImportError: # pragma: no cover
return 'IronPython'
return 'CPython'
-try:
- import sysconfig
-except ImportError: # pragma: no cover
- from ._backport import sysconfig
+import shutil
+import sysconfig
try:
callable = callable
@@ -618,18 +615,15 @@ except ImportError: # pragma: no cover
try:
from importlib.util import cache_from_source # Python >= 3.4
except ImportError: # pragma: no cover
- try:
- from imp import cache_from_source
- except ImportError: # pragma: no cover
- def cache_from_source(path, debug_override=None):
- assert path.endswith('.py')
- if debug_override is None:
- debug_override = __debug__
- if debug_override:
- suffix = 'c'
- else:
- suffix = 'o'
- return path + suffix
+ def cache_from_source(path, debug_override=None):
+ assert path.endswith('.py')
+ if debug_override is None:
+ debug_override = __debug__
+ if debug_override:
+ suffix = 'c'
+ else:
+ suffix = 'o'
+ return path + suffix
try:
from collections import OrderedDict
diff --git a/src/pip/_vendor/distlib/database.py b/src/pip/_vendor/distlib/database.py
index 0a90c300b..5db5d7f50 100644
--- a/src/pip/_vendor/distlib/database.py
+++ b/src/pip/_vendor/distlib/database.py
@@ -132,29 +132,35 @@ class DistributionPath(object):
r = finder.find(entry)
if not r or r.path in seen:
continue
- if self._include_dist and entry.endswith(DISTINFO_EXT):
- possible_filenames = [METADATA_FILENAME,
- WHEEL_METADATA_FILENAME,
- LEGACY_METADATA_FILENAME]
- for metadata_filename in possible_filenames:
- metadata_path = posixpath.join(entry, metadata_filename)
- pydist = finder.find(metadata_path)
- if pydist:
- break
- else:
- continue
+ try:
+ if self._include_dist and entry.endswith(DISTINFO_EXT):
+ possible_filenames = [METADATA_FILENAME,
+ WHEEL_METADATA_FILENAME,
+ LEGACY_METADATA_FILENAME]
+ for metadata_filename in possible_filenames:
+ metadata_path = posixpath.join(entry, metadata_filename)
+ pydist = finder.find(metadata_path)
+ if pydist:
+ break
+ else:
+ continue
- with contextlib.closing(pydist.as_stream()) as stream:
- metadata = Metadata(fileobj=stream, scheme='legacy')
- logger.debug('Found %s', r.path)
- seen.add(r.path)
- yield new_dist_class(r.path, metadata=metadata,
- env=self)
- elif self._include_egg and entry.endswith(('.egg-info',
- '.egg')):
- logger.debug('Found %s', r.path)
- seen.add(r.path)
- yield old_dist_class(r.path, self)
+ with contextlib.closing(pydist.as_stream()) as stream:
+ metadata = Metadata(fileobj=stream, scheme='legacy')
+ logger.debug('Found %s', r.path)
+ seen.add(r.path)
+ yield new_dist_class(r.path, metadata=metadata,
+ env=self)
+ elif self._include_egg and entry.endswith(('.egg-info',
+ '.egg')):
+ logger.debug('Found %s', r.path)
+ seen.add(r.path)
+ yield old_dist_class(r.path, self)
+ except Exception as e:
+ msg = 'Unable to read distribution at %s, perhaps due to bad metadata: %s'
+ logger.warning(msg, r.path, e)
+ import warnings
+ warnings.warn(msg % (r.path, e), stacklevel=2)
def _generate_cache(self):
"""
@@ -379,8 +385,9 @@ class Distribution(object):
def _get_requirements(self, req_attr):
md = self.metadata
- logger.debug('Getting requirements from metadata %r', md.todict())
reqts = getattr(md, req_attr)
+ logger.debug('%s: got requirements %r from metadata: %r', self.name, req_attr,
+ reqts)
return set(md.get_requirements(reqts, extras=self.extras,
env=self.context))
@@ -1308,22 +1315,26 @@ def get_required_dists(dists, dist):
:param dists: a list of distributions
:param dist: a distribution, member of *dists* for which we are interested
+ in finding the dependencies.
"""
if dist not in dists:
raise DistlibException('given distribution %r is not a member '
'of the list' % dist.name)
graph = make_graph(dists)
- req = [] # required distributions
+ req = set() # required distributions
todo = graph.adjacency_list[dist] # list of nodes we should inspect
+ seen = set(t[0] for t in todo) # already added to todo
while todo:
d = todo.pop()[0]
- req.append(d)
- for pred in graph.adjacency_list[d]:
- if pred not in req:
+ req.add(d)
+ pred_list = graph.adjacency_list[d]
+ for pred in pred_list:
+ d = pred[0]
+ if d not in req and d not in seen:
+ seen.add(d)
todo.append(pred)
-
return req
diff --git a/src/pip/_vendor/distlib/index.py b/src/pip/_vendor/distlib/index.py
index b1fbbf8e8..9b6d129ed 100644
--- a/src/pip/_vendor/distlib/index.py
+++ b/src/pip/_vendor/distlib/index.py
@@ -12,7 +12,7 @@ import subprocess
import tempfile
try:
from threading import Thread
-except ImportError:
+except ImportError: # pragma: no cover
from dummy_threading import Thread
from . import DistlibException
@@ -104,7 +104,7 @@ class PackageIndex(object):
pm.add_password(self.realm, netloc, self.username, self.password)
self.password_handler = HTTPBasicAuthHandler(pm)
- def register(self, metadata):
+ def register(self, metadata): # pragma: no cover
"""
Register a distribution on PyPI, using the provided metadata.
@@ -142,8 +142,7 @@ class PackageIndex(object):
logger.debug('%s: %s' % (name, s))
stream.close()
- def get_sign_command(self, filename, signer, sign_password,
- keystore=None):
+ def get_sign_command(self, filename, signer, sign_password, keystore=None): # pragma: no cover
"""
Return a suitable command for signing a file.
@@ -206,7 +205,7 @@ class PackageIndex(object):
t2.join()
return p.returncode, stdout, stderr
- def sign_file(self, filename, signer, sign_password, keystore=None):
+ def sign_file(self, filename, signer, sign_password, keystore=None): # pragma: no cover
"""
Sign a file.
@@ -286,7 +285,7 @@ class PackageIndex(object):
request = self.encode_request(d.items(), files)
return self.send_request(request)
- def upload_documentation(self, metadata, doc_dir):
+ def upload_documentation(self, metadata, doc_dir): # pragma: no cover
"""
Upload documentation to the index.
@@ -499,7 +498,7 @@ class PackageIndex(object):
}
return Request(self.url, body, headers)
- def search(self, terms, operator=None):
+ def search(self, terms, operator=None): # pragma: no cover
if isinstance(terms, string_types):
terms = {'name': terms}
rpc_proxy = ServerProxy(self.url, timeout=3.0)
diff --git a/src/pip/_vendor/distlib/locators.py b/src/pip/_vendor/distlib/locators.py
index 0c7d63914..966ebc0e3 100644
--- a/src/pip/_vendor/distlib/locators.py
+++ b/src/pip/_vendor/distlib/locators.py
@@ -633,7 +633,7 @@ class SimpleScrapingLocator(Locator):
self._threads = []
for i in range(self.num_workers):
t = threading.Thread(target=self._fetch)
- t.setDaemon(True)
+ t.daemon = True
t.start()
self._threads.append(t)
@@ -1053,9 +1053,9 @@ class AggregatingLocator(Locator):
# We use a legacy scheme simply because most of the dists on PyPI use legacy
-# versions which don't conform to PEP 426 / PEP 440.
+# versions which don't conform to PEP 440.
default_locator = AggregatingLocator(
- JSONLocator(),
+ # JSONLocator(), # don't use as PEP 426 is withdrawn
SimpleScrapingLocator('https://pypi.org/simple/',
timeout=3.0),
scheme='legacy')
diff --git a/src/pip/_vendor/distlib/markers.py b/src/pip/_vendor/distlib/markers.py
index b43136fa1..9dc684103 100644
--- a/src/pip/_vendor/distlib/markers.py
+++ b/src/pip/_vendor/distlib/markers.py
@@ -90,6 +90,8 @@ class Evaluator(object):
result = self.operations[op](lhs, rhs)
return result
+_DIGITS = re.compile(r'\d+\.\d+')
+
def default_context():
def format_full_version(info):
version = '%s.%s.%s' % (info.major, info.minor, info.micro)
@@ -105,6 +107,9 @@ def default_context():
implementation_version = '0'
implementation_name = ''
+ ppv = platform.python_version()
+ m = _DIGITS.match(ppv)
+ pv = m.group(0)
result = {
'implementation_name': implementation_name,
'implementation_version': implementation_version,
@@ -115,8 +120,8 @@ def default_context():
'platform_system': platform.system(),
'platform_version': platform.version(),
'platform_in_venv': str(in_venv()),
- 'python_full_version': platform.python_version(),
- 'python_version': platform.python_version()[:3],
+ 'python_full_version': ppv,
+ 'python_version': pv,
'sys_platform': sys.platform,
}
return result
diff --git a/src/pip/_vendor/distlib/metadata.py b/src/pip/_vendor/distlib/metadata.py
index 6a26b0ab2..c329e1977 100644
--- a/src/pip/_vendor/distlib/metadata.py
+++ b/src/pip/_vendor/distlib/metadata.py
@@ -5,7 +5,7 @@
#
"""Implementation of the Metadata for Python packages PEPs.
-Supports all metadata formats (1.0, 1.1, 1.2, 1.3/2.1 and withdrawn 2.0).
+Supports all metadata formats (1.0, 1.1, 1.2, 1.3/2.1 and 2.2).
"""
from __future__ import unicode_literals
@@ -100,12 +100,17 @@ _566_FIELDS = _426_FIELDS + ('Description-Content-Type',
_566_MARKERS = ('Description-Content-Type',)
+_643_MARKERS = ('Dynamic', 'License-File')
+
+_643_FIELDS = _566_FIELDS + _643_MARKERS
+
_ALL_FIELDS = set()
_ALL_FIELDS.update(_241_FIELDS)
_ALL_FIELDS.update(_314_FIELDS)
_ALL_FIELDS.update(_345_FIELDS)
_ALL_FIELDS.update(_426_FIELDS)
_ALL_FIELDS.update(_566_FIELDS)
+_ALL_FIELDS.update(_643_FIELDS)
EXTRA_RE = re.compile(r'''extra\s*==\s*("([^"]+)"|'([^']+)')''')
@@ -121,7 +126,10 @@ def _version2fieldlist(version):
# avoid adding field names if already there
return _345_FIELDS + tuple(f for f in _566_FIELDS if f not in _345_FIELDS)
elif version == '2.0':
- return _426_FIELDS
+ raise ValueError('Metadata 2.0 is withdrawn and not supported')
+ # return _426_FIELDS
+ elif version == '2.2':
+ return _643_FIELDS
raise MetadataUnrecognizedVersionError(version)
@@ -139,7 +147,7 @@ def _best_version(fields):
continue
keys.append(key)
- possible_versions = ['1.0', '1.1', '1.2', '1.3', '2.0', '2.1']
+ possible_versions = ['1.0', '1.1', '1.2', '1.3', '2.1', '2.2'] # 2.0 removed
# first let's try to see if a field is not part of one of the version
for key in keys:
@@ -159,9 +167,12 @@ def _best_version(fields):
if key != 'Description': # In 2.1, description allowed after headers
possible_versions.remove('2.1')
logger.debug('Removed 2.1 due to %s', key)
- if key not in _426_FIELDS and '2.0' in possible_versions:
- possible_versions.remove('2.0')
- logger.debug('Removed 2.0 due to %s', key)
+ if key not in _643_FIELDS and '2.2' in possible_versions:
+ possible_versions.remove('2.2')
+ logger.debug('Removed 2.2 due to %s', key)
+ # if key not in _426_FIELDS and '2.0' in possible_versions:
+ # possible_versions.remove('2.0')
+ # logger.debug('Removed 2.0 due to %s', key)
# possible_version contains qualified versions
if len(possible_versions) == 1:
@@ -174,16 +185,18 @@ def _best_version(fields):
is_1_1 = '1.1' in possible_versions and _has_marker(keys, _314_MARKERS)
is_1_2 = '1.2' in possible_versions and _has_marker(keys, _345_MARKERS)
is_2_1 = '2.1' in possible_versions and _has_marker(keys, _566_MARKERS)
- is_2_0 = '2.0' in possible_versions and _has_marker(keys, _426_MARKERS)
- if int(is_1_1) + int(is_1_2) + int(is_2_1) + int(is_2_0) > 1:
- raise MetadataConflictError('You used incompatible 1.1/1.2/2.0/2.1 fields')
+ # is_2_0 = '2.0' in possible_versions and _has_marker(keys, _426_MARKERS)
+ is_2_2 = '2.2' in possible_versions and _has_marker(keys, _643_MARKERS)
+ if int(is_1_1) + int(is_1_2) + int(is_2_1) + int(is_2_2) > 1:
+ raise MetadataConflictError('You used incompatible 1.1/1.2/2.1/2.2 fields')
- # we have the choice, 1.0, or 1.2, or 2.0
+ # we have the choice, 1.0, or 1.2, 2.1 or 2.2
# - 1.0 has a broken Summary field but works with all tools
# - 1.1 is to avoid
# - 1.2 fixes Summary but has little adoption
- # - 2.0 adds more features and is very new
- if not is_1_1 and not is_1_2 and not is_2_1 and not is_2_0:
+ # - 2.1 adds more features
+ # - 2.2 is the latest
+ if not is_1_1 and not is_1_2 and not is_2_1 and not is_2_2:
# we couldn't find any specific marker
if PKG_INFO_PREFERRED_VERSION in possible_versions:
return PKG_INFO_PREFERRED_VERSION
@@ -193,8 +206,10 @@ def _best_version(fields):
return '1.2'
if is_2_1:
return '2.1'
+ # if is_2_2:
+ # return '2.2'
- return '2.0'
+ return '2.2'
# This follows the rules about transforming keys as described in
# https://www.python.org/dev/peps/pep-0566/#id17
@@ -210,7 +225,7 @@ _LISTFIELDS = ('Platform', 'Classifier', 'Obsoletes',
'Requires', 'Provides', 'Obsoletes-Dist',
'Provides-Dist', 'Requires-Dist', 'Requires-External',
'Project-URL', 'Supported-Platform', 'Setup-Requires-Dist',
- 'Provides-Extra', 'Extension')
+ 'Provides-Extra', 'Extension', 'License-File')
_LISTTUPLEFIELDS = ('Project-URL',)
_ELEMENTSFIELD = ('Keywords',)
@@ -602,7 +617,7 @@ LEGACY_METADATA_FILENAME = 'METADATA'
class Metadata(object):
"""
- The metadata of a release. This implementation uses 2.0 (JSON)
+ The metadata of a release. This implementation uses 2.1
metadata where possible. If not possible, it wraps a LegacyMetadata
instance which handles the key-value metadata format.
"""
@@ -611,6 +626,8 @@ class Metadata(object):
NAME_MATCHER = re.compile('^[0-9A-Z]([0-9A-Z_.-]*[0-9A-Z])?$', re.I)
+ FIELDNAME_MATCHER = re.compile('^[A-Z]([0-9A-Z-]*[0-9A-Z])?$', re.I)
+
VERSION_MATCHER = PEP440_VERSION_RE
SUMMARY_MATCHER = re.compile('.{1,2047}')
@@ -638,6 +655,7 @@ class Metadata(object):
'name': (NAME_MATCHER, ('legacy',)),
'version': (VERSION_MATCHER, ('legacy',)),
'summary': (SUMMARY_MATCHER, ('legacy',)),
+ 'dynamic': (FIELDNAME_MATCHER, ('legacy',)),
}
__slots__ = ('_legacy', '_data', 'scheme')
diff --git a/src/pip/_vendor/distlib/scripts.py b/src/pip/_vendor/distlib/scripts.py
index 913912c7b..d2706242b 100644
--- a/src/pip/_vendor/distlib/scripts.py
+++ b/src/pip/_vendor/distlib/scripts.py
@@ -10,6 +10,8 @@ import os
import re
import struct
import sys
+import time
+from zipfile import ZipInfo
from .compat import sysconfig, detect_encoding, ZipFile
from .resources import finder
@@ -249,7 +251,13 @@ class ScriptMaker(object):
launcher = self._get_launcher('w')
stream = BytesIO()
with ZipFile(stream, 'w') as zf:
- zf.writestr('__main__.py', script_bytes)
+ source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH')
+ if source_date_epoch:
+ date_time = time.gmtime(int(source_date_epoch))[:6]
+ zinfo = ZipInfo(filename='__main__.py', date_time=date_time)
+ zf.writestr(zinfo, script_bytes)
+ else:
+ zf.writestr('__main__.py', script_bytes)
zip_data = stream.getvalue()
script_bytes = launcher + shebang + zip_data
for name in names:
diff --git a/src/pip/_vendor/distlib/t32.exe b/src/pip/_vendor/distlib/t32.exe
index 8932a18e4..52154f0be 100644
--- a/src/pip/_vendor/distlib/t32.exe
+++ b/src/pip/_vendor/distlib/t32.exe
Binary files differ
diff --git a/src/pip/_vendor/distlib/t64-arm.exe b/src/pip/_vendor/distlib/t64-arm.exe
index c5df4869d..e1ab8f8f5 100644
--- a/src/pip/_vendor/distlib/t64-arm.exe
+++ b/src/pip/_vendor/distlib/t64-arm.exe
Binary files differ
diff --git a/src/pip/_vendor/distlib/t64.exe b/src/pip/_vendor/distlib/t64.exe
index 325b8057c..e8bebdba6 100644
--- a/src/pip/_vendor/distlib/t64.exe
+++ b/src/pip/_vendor/distlib/t64.exe
Binary files differ
diff --git a/src/pip/_vendor/distlib/util.py b/src/pip/_vendor/distlib/util.py
index 80bfc864b..dd01849d9 100644
--- a/src/pip/_vendor/distlib/util.py
+++ b/src/pip/_vendor/distlib/util.py
@@ -1432,29 +1432,19 @@ if ssl:
self.sock = sock
self._tunnel()
- if not hasattr(ssl, 'SSLContext'):
- # For 2.x
- if self.ca_certs:
- cert_reqs = ssl.CERT_REQUIRED
- else:
- cert_reqs = ssl.CERT_NONE
- self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
- cert_reqs=cert_reqs,
- ssl_version=ssl.PROTOCOL_SSLv23,
- ca_certs=self.ca_certs)
- else: # pragma: no cover
- context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
- if hasattr(ssl, 'OP_NO_SSLv2'):
- context.options |= ssl.OP_NO_SSLv2
- if self.cert_file:
- context.load_cert_chain(self.cert_file, self.key_file)
- kwargs = {}
- if self.ca_certs:
- context.verify_mode = ssl.CERT_REQUIRED
- context.load_verify_locations(cafile=self.ca_certs)
- if getattr(ssl, 'HAS_SNI', False):
- kwargs['server_hostname'] = self.host
- self.sock = context.wrap_socket(sock, **kwargs)
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ if hasattr(ssl, 'OP_NO_SSLv2'):
+ context.options |= ssl.OP_NO_SSLv2
+ if self.cert_file:
+ context.load_cert_chain(self.cert_file, self.key_file)
+ kwargs = {}
+ if self.ca_certs:
+ context.verify_mode = ssl.CERT_REQUIRED
+ context.load_verify_locations(cafile=self.ca_certs)
+ if getattr(ssl, 'HAS_SNI', False):
+ kwargs['server_hostname'] = self.host
+
+ self.sock = context.wrap_socket(sock, **kwargs)
if self.ca_certs and self.check_domain:
try:
match_hostname(self.sock.getpeercert(), self.host)
@@ -1513,25 +1503,6 @@ if ssl:
#
# XML-RPC with timeouts
#
-
-_ver_info = sys.version_info[:2]
-
-if _ver_info == (2, 6):
- class HTTP(httplib.HTTP):
- def __init__(self, host='', port=None, **kwargs):
- if port == 0: # 0 means use port 0, not the default port
- port = None
- self._setup(self._connection_class(host, port, **kwargs))
-
-
- if ssl:
- class HTTPS(httplib.HTTPS):
- def __init__(self, host='', port=None, **kwargs):
- if port == 0: # 0 means use port 0, not the default port
- port = None
- self._setup(self._connection_class(host, port, **kwargs))
-
-
class Transport(xmlrpclib.Transport):
def __init__(self, timeout, use_datetime=0):
self.timeout = timeout
@@ -1539,14 +1510,10 @@ class Transport(xmlrpclib.Transport):
def make_connection(self, host):
h, eh, x509 = self.get_host_info(host)
- if _ver_info == (2, 6):
- result = HTTP(h, timeout=self.timeout)
- else:
- if not self._connection or host != self._connection[0]:
- self._extra_headers = eh
- self._connection = host, httplib.HTTPConnection(h)
- result = self._connection[1]
- return result
+ if not self._connection or host != self._connection[0]:
+ self._extra_headers = eh
+ self._connection = host, httplib.HTTPConnection(h)
+ return self._connection[1]
if ssl:
class SafeTransport(xmlrpclib.SafeTransport):
@@ -1559,15 +1526,11 @@ if ssl:
if not kwargs:
kwargs = {}
kwargs['timeout'] = self.timeout
- if _ver_info == (2, 6):
- result = HTTPS(host, None, **kwargs)
- else:
- if not self._connection or host != self._connection[0]:
- self._extra_headers = eh
- self._connection = host, httplib.HTTPSConnection(h, None,
- **kwargs)
- result = self._connection[1]
- return result
+ if not self._connection or host != self._connection[0]:
+ self._extra_headers = eh
+ self._connection = host, httplib.HTTPSConnection(h, None,
+ **kwargs)
+ return self._connection[1]
class ServerProxy(xmlrpclib.ServerProxy):
diff --git a/src/pip/_vendor/distlib/w32.exe b/src/pip/_vendor/distlib/w32.exe
index e6439e9e4..4ee2d3a31 100644
--- a/src/pip/_vendor/distlib/w32.exe
+++ b/src/pip/_vendor/distlib/w32.exe
Binary files differ
diff --git a/src/pip/_vendor/distlib/w64-arm.exe b/src/pip/_vendor/distlib/w64-arm.exe
index 70a2ec268..951d5817c 100644
--- a/src/pip/_vendor/distlib/w64-arm.exe
+++ b/src/pip/_vendor/distlib/w64-arm.exe
Binary files differ
diff --git a/src/pip/_vendor/distlib/w64.exe b/src/pip/_vendor/distlib/w64.exe
index 46139dbf9..5763076d2 100644
--- a/src/pip/_vendor/distlib/w64.exe
+++ b/src/pip/_vendor/distlib/w64.exe
Binary files differ
diff --git a/src/pip/_vendor/distlib/wheel.py b/src/pip/_vendor/distlib/wheel.py
index 48abfde5b..028c2d99b 100644
--- a/src/pip/_vendor/distlib/wheel.py
+++ b/src/pip/_vendor/distlib/wheel.py
@@ -11,7 +11,6 @@ import codecs
import datetime
from email import message_from_file
import hashlib
-import imp
import json
import logging
import os
@@ -61,10 +60,18 @@ else:
parts = ['cp', VER_SUFFIX]
if sysconfig.get_config_var('Py_DEBUG'):
parts.append('d')
- if sysconfig.get_config_var('WITH_PYMALLOC'):
- parts.append('m')
- if sysconfig.get_config_var('Py_UNICODE_SIZE') == 4:
- parts.append('u')
+ if IMP_PREFIX == 'cp':
+ vi = sys.version_info[:2]
+ if vi < (3, 8):
+ wpm = sysconfig.get_config_var('WITH_PYMALLOC')
+ if wpm is None:
+ wpm = True
+ if wpm:
+ parts.append('m')
+ if vi < (3, 3):
+ us = sysconfig.get_config_var('Py_UNICODE_SIZE')
+ if us == 4 or (us is None and sys.maxunicode == 0x10FFFF):
+ parts.append('u')
return ''.join(parts)
ABI = _derive_abi()
del _derive_abi
@@ -95,6 +102,29 @@ if os.sep == '/':
else:
to_posix = lambda o: o.replace(os.sep, '/')
+if sys.version_info[0] < 3:
+ import imp
+else:
+ imp = None
+ import importlib.machinery
+ import importlib.util
+
+def _get_suffixes():
+ if imp:
+ return [s[0] for s in imp.get_suffixes()]
+ else:
+ return importlib.machinery.EXTENSION_SUFFIXES
+
+def _load_dynamic(name, path):
+ # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
+ if imp:
+ return imp.load_dynamic(name, path)
+ else:
+ spec = importlib.util.spec_from_file_location(name, path)
+ module = importlib.util.module_from_spec(spec)
+ sys.modules[name] = module
+ spec.loader.exec_module(module)
+ return module
class Mounter(object):
def __init__(self):
@@ -124,7 +154,7 @@ class Mounter(object):
else:
if fullname not in self.libs:
raise ImportError('unable to find extension for %s' % fullname)
- result = imp.load_dynamic(fullname, self.libs[fullname])
+ result = _load_dynamic(fullname, self.libs[fullname])
result.__loader__ = self
parts = fullname.rsplit('.', 1)
if len(parts) > 1:
@@ -301,10 +331,9 @@ class Wheel(object):
result = base64.urlsafe_b64encode(result).rstrip(b'=').decode('ascii')
return hash_kind, result
- def write_record(self, records, record_path, base):
+ def write_record(self, records, record_path, archive_record_path):
records = list(records) # make a copy, as mutated
- p = to_posix(os.path.relpath(record_path, base))
- records.append((p, '', ''))
+ records.append((archive_record_path, '', ''))
with CSVWriter(record_path) as writer:
for row in records:
writer.writerow(row)
@@ -321,8 +350,8 @@ class Wheel(object):
records.append((ap, digest, size))
p = os.path.join(distinfo, 'RECORD')
- self.write_record(records, p, libdir)
ap = to_posix(os.path.join(info_dir, 'RECORD'))
+ self.write_record(records, p, ap)
archive_paths.append((ap, p))
def build_zip(self, pathname, archive_paths):
@@ -965,7 +994,7 @@ def compatible_tags():
versions.append(''.join([major, str(minor)]))
abis = []
- for suffix, _, _ in imp.get_suffixes():
+ for suffix in _get_suffixes():
if suffix.startswith('.abi'):
abis.append(suffix.split('.', 2)[1])
abis.sort()
diff --git a/src/pip/_vendor/distro.LICENSE b/src/pip/_vendor/distro/LICENSE
index e06d20818..e06d20818 100644
--- a/src/pip/_vendor/distro.LICENSE
+++ b/src/pip/_vendor/distro/LICENSE
diff --git a/src/pip/_vendor/distro/__init__.py b/src/pip/_vendor/distro/__init__.py
new file mode 100644
index 000000000..7686fe85a
--- /dev/null
+++ b/src/pip/_vendor/distro/__init__.py
@@ -0,0 +1,54 @@
+from .distro import (
+ NORMALIZED_DISTRO_ID,
+ NORMALIZED_LSB_ID,
+ NORMALIZED_OS_ID,
+ LinuxDistribution,
+ __version__,
+ build_number,
+ codename,
+ distro_release_attr,
+ distro_release_info,
+ id,
+ info,
+ like,
+ linux_distribution,
+ lsb_release_attr,
+ lsb_release_info,
+ major_version,
+ minor_version,
+ name,
+ os_release_attr,
+ os_release_info,
+ uname_attr,
+ uname_info,
+ version,
+ version_parts,
+)
+
+__all__ = [
+ "NORMALIZED_DISTRO_ID",
+ "NORMALIZED_LSB_ID",
+ "NORMALIZED_OS_ID",
+ "LinuxDistribution",
+ "build_number",
+ "codename",
+ "distro_release_attr",
+ "distro_release_info",
+ "id",
+ "info",
+ "like",
+ "linux_distribution",
+ "lsb_release_attr",
+ "lsb_release_info",
+ "major_version",
+ "minor_version",
+ "name",
+ "os_release_attr",
+ "os_release_info",
+ "uname_attr",
+ "uname_info",
+ "version",
+ "version_parts",
+]
+
+__version__ = __version__
diff --git a/src/pip/_vendor/distro/__main__.py b/src/pip/_vendor/distro/__main__.py
new file mode 100644
index 000000000..0c01d5b08
--- /dev/null
+++ b/src/pip/_vendor/distro/__main__.py
@@ -0,0 +1,4 @@
+from .distro import main
+
+if __name__ == "__main__":
+ main()
diff --git a/src/pip/_vendor/distro.py b/src/pip/_vendor/distro/distro.py
index 789274134..89e186804 100644
--- a/src/pip/_vendor/distro.py
+++ b/src/pip/_vendor/distro/distro.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
# Copyright 2015,2016,2017 Nir Cohen
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -36,40 +37,39 @@ import shlex
import subprocess
import sys
import warnings
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ Iterable,
+ Optional,
+ Sequence,
+ TextIO,
+ Tuple,
+ Type,
+)
-__version__ = "1.6.0"
-
-# Use `if False` to avoid an ImportError on Python 2. After dropping Python 2
-# support, can use typing.TYPE_CHECKING instead. See:
-# https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING
-if False: # pragma: nocover
- from typing import (
- Any,
- Callable,
- Dict,
- Iterable,
- Optional,
- Sequence,
- TextIO,
- Tuple,
- Type,
- TypedDict,
- Union,
- )
+try:
+ from typing import TypedDict
+except ImportError:
+ # Python 3.7
+ TypedDict = dict
- VersionDict = TypedDict(
- "VersionDict", {"major": str, "minor": str, "build_number": str}
- )
- InfoDict = TypedDict(
- "InfoDict",
- {
- "id": str,
- "version": str,
- "version_parts": VersionDict,
- "like": str,
- "codename": str,
- },
- )
+__version__ = "1.8.0"
+
+
+class VersionDict(TypedDict):
+ major: str
+ minor: str
+ build_number: str
+
+
+class InfoDict(TypedDict):
+ id: str
+ version: str
+ version_parts: VersionDict
+ like: str
+ codename: str
_UNIXCONFDIR = os.environ.get("UNIXCONFDIR", "/etc")
@@ -85,6 +85,7 @@ _OS_RELEASE_BASENAME = "os-release"
#: * Value: Normalized value.
NORMALIZED_OS_ID = {
"ol": "oracle", # Oracle Linux
+ "opensuse-leap": "opensuse", # Newer versions of OpenSuSE report as opensuse-leap
}
#: Translation table for normalizing the "Distributor ID" attribute returned by
@@ -121,6 +122,26 @@ _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN = re.compile(
# Pattern for base file name of distro release file
_DISTRO_RELEASE_BASENAME_PATTERN = re.compile(r"(\w+)[-_](release|version)$")
+# Base file names to be looked up for if _UNIXCONFDIR is not readable.
+_DISTRO_RELEASE_BASENAMES = [
+ "SuSE-release",
+ "arch-release",
+ "base-release",
+ "centos-release",
+ "fedora-release",
+ "gentoo-release",
+ "mageia-release",
+ "mandrake-release",
+ "mandriva-release",
+ "mandrivalinux-release",
+ "manjaro-release",
+ "oracle-release",
+ "redhat-release",
+ "rocky-release",
+ "sl-release",
+ "slackware-version",
+]
+
# Base file names to be ignored when searching for distro release file
_DISTRO_RELEASE_IGNORE_BASENAMES = (
"debian_version",
@@ -133,8 +154,7 @@ _DISTRO_RELEASE_IGNORE_BASENAMES = (
)
-def linux_distribution(full_distribution_name=True):
- # type: (bool) -> Tuple[str, str, str]
+def linux_distribution(full_distribution_name: bool = True) -> Tuple[str, str, str]:
"""
.. deprecated:: 1.6.0
@@ -151,7 +171,8 @@ def linux_distribution(full_distribution_name=True):
* ``version``: The result of :func:`distro.version`.
- * ``codename``: The result of :func:`distro.codename`.
+ * ``codename``: The extra item (usually in parentheses) after the
+ os-release version number, or the result of :func:`distro.codename`.
The interface of this function is compatible with the original
:py:func:`platform.linux_distribution` function, supporting a subset of
@@ -176,8 +197,7 @@ def linux_distribution(full_distribution_name=True):
return _distro.linux_distribution(full_distribution_name)
-def id():
- # type: () -> str
+def id() -> str:
"""
Return the distro ID of the current distribution, as a
machine-readable string.
@@ -198,8 +218,9 @@ def id():
"fedora" Fedora
"sles" SUSE Linux Enterprise Server
"opensuse" openSUSE
- "amazon" Amazon Linux
+ "amzn" Amazon Linux
"arch" Arch Linux
+ "buildroot" Buildroot
"cloudlinux" CloudLinux OS
"exherbo" Exherbo Linux
"gentoo" GenToo Linux
@@ -219,6 +240,9 @@ def id():
"netbsd" NetBSD
"freebsd" FreeBSD
"midnightbsd" MidnightBSD
+ "rocky" Rocky Linux
+ "aix" AIX
+ "guix" Guix System
============== =========================================
If you have a need to get distros for reliable IDs added into this set,
@@ -256,8 +280,7 @@ def id():
return _distro.id()
-def name(pretty=False):
- # type: (bool) -> str
+def name(pretty: bool = False) -> str:
"""
Return the name of the current OS distribution, as a human-readable
string.
@@ -296,8 +319,7 @@ def name(pretty=False):
return _distro.name(pretty)
-def version(pretty=False, best=False):
- # type: (bool, bool) -> str
+def version(pretty: bool = False, best: bool = False) -> str:
"""
Return the version of the current OS distribution, as a human-readable
string.
@@ -313,6 +335,10 @@ def version(pretty=False, best=False):
sources in a fixed priority order does not always yield the most precise
version (e.g. for Debian 8.2, or CentOS 7.1).
+ Some other distributions may not provide this kind of information. In these
+ cases, an empty string would be returned. This behavior can be observed
+ with rolling releases distributions (e.g. Arch Linux).
+
The *best* parameter can be used to control the approach for the returned
version:
@@ -341,8 +367,7 @@ def version(pretty=False, best=False):
return _distro.version(pretty, best)
-def version_parts(best=False):
- # type: (bool) -> Tuple[str, str, str]
+def version_parts(best: bool = False) -> Tuple[str, str, str]:
"""
Return the version of the current OS distribution as a tuple
``(major, minor, build_number)`` with items as follows:
@@ -359,8 +384,7 @@ def version_parts(best=False):
return _distro.version_parts(best)
-def major_version(best=False):
- # type: (bool) -> str
+def major_version(best: bool = False) -> str:
"""
Return the major version of the current OS distribution, as a string,
if provided.
@@ -373,8 +397,7 @@ def major_version(best=False):
return _distro.major_version(best)
-def minor_version(best=False):
- # type: (bool) -> str
+def minor_version(best: bool = False) -> str:
"""
Return the minor version of the current OS distribution, as a string,
if provided.
@@ -387,8 +410,7 @@ def minor_version(best=False):
return _distro.minor_version(best)
-def build_number(best=False):
- # type: (bool) -> str
+def build_number(best: bool = False) -> str:
"""
Return the build number of the current OS distribution, as a string,
if provided.
@@ -401,8 +423,7 @@ def build_number(best=False):
return _distro.build_number(best)
-def like():
- # type: () -> str
+def like() -> str:
"""
Return a space-separated list of distro IDs of distributions that are
closely related to the current OS distribution in regards to packaging
@@ -419,8 +440,7 @@ def like():
return _distro.like()
-def codename():
- # type: () -> str
+def codename() -> str:
"""
Return the codename for the release of the current OS distribution,
as a string.
@@ -444,8 +464,7 @@ def codename():
return _distro.codename()
-def info(pretty=False, best=False):
- # type: (bool, bool) -> InfoDict
+def info(pretty: bool = False, best: bool = False) -> InfoDict:
"""
Return certain machine-readable information items about the current OS
distribution in a dictionary, as shown in the following example:
@@ -489,8 +508,7 @@ def info(pretty=False, best=False):
return _distro.info(pretty, best)
-def os_release_info():
- # type: () -> Dict[str, str]
+def os_release_info() -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information items
from the os-release file data source of the current OS distribution.
@@ -500,8 +518,7 @@ def os_release_info():
return _distro.os_release_info()
-def lsb_release_info():
- # type: () -> Dict[str, str]
+def lsb_release_info() -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information items
from the lsb_release command data source of the current OS distribution.
@@ -512,8 +529,7 @@ def lsb_release_info():
return _distro.lsb_release_info()
-def distro_release_info():
- # type: () -> Dict[str, str]
+def distro_release_info() -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information items
from the distro release file data source of the current OS distribution.
@@ -523,8 +539,7 @@ def distro_release_info():
return _distro.distro_release_info()
-def uname_info():
- # type: () -> Dict[str, str]
+def uname_info() -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information items
from the distro release file data source of the current OS distribution.
@@ -532,8 +547,7 @@ def uname_info():
return _distro.uname_info()
-def os_release_attr(attribute):
- # type: (str) -> str
+def os_release_attr(attribute: str) -> str:
"""
Return a single named information item from the os-release file data source
of the current OS distribution.
@@ -552,8 +566,7 @@ def os_release_attr(attribute):
return _distro.os_release_attr(attribute)
-def lsb_release_attr(attribute):
- # type: (str) -> str
+def lsb_release_attr(attribute: str) -> str:
"""
Return a single named information item from the lsb_release command output
data source of the current OS distribution.
@@ -573,8 +586,7 @@ def lsb_release_attr(attribute):
return _distro.lsb_release_attr(attribute)
-def distro_release_attr(attribute):
- # type: (str) -> str
+def distro_release_attr(attribute: str) -> str:
"""
Return a single named information item from the distro release file
data source of the current OS distribution.
@@ -593,8 +605,7 @@ def distro_release_attr(attribute):
return _distro.distro_release_attr(attribute)
-def uname_attr(attribute):
- # type: (str) -> str
+def uname_attr(attribute: str) -> str:
"""
Return a single named information item from the distro release file
data source of the current OS distribution.
@@ -615,25 +626,23 @@ try:
from functools import cached_property
except ImportError:
# Python < 3.8
- class cached_property(object): # type: ignore
+ class cached_property: # type: ignore
"""A version of @property which caches the value. On access, it calls the
underlying function and sets the value in `__dict__` so future accesses
will not re-call the property.
"""
- def __init__(self, f):
- # type: (Callable[[Any], Any]) -> None
+ def __init__(self, f: Callable[[Any], Any]) -> None:
self._fname = f.__name__
self._f = f
- def __get__(self, obj, owner):
- # type: (Any, Type[Any]) -> Any
- assert obj is not None, "call {} on an instance".format(self._fname)
+ def __get__(self, obj: Any, owner: Type[Any]) -> Any:
+ assert obj is not None, f"call {self._fname} on an instance"
ret = obj.__dict__[self._fname] = self._f(obj)
return ret
-class LinuxDistribution(object):
+class LinuxDistribution:
"""
Provides information about a OS distribution.
@@ -653,13 +662,13 @@ class LinuxDistribution(object):
def __init__(
self,
- include_lsb=True,
- os_release_file="",
- distro_release_file="",
- include_uname=True,
- root_dir=None,
- ):
- # type: (bool, str, str, bool, Optional[str]) -> None
+ include_lsb: Optional[bool] = None,
+ os_release_file: str = "",
+ distro_release_file: str = "",
+ include_uname: Optional[bool] = None,
+ root_dir: Optional[str] = None,
+ include_oslevel: Optional[bool] = None,
+ ) -> None:
"""
The initialization method of this class gathers information from the
available data sources, and stores that in private instance attributes.
@@ -699,7 +708,13 @@ class LinuxDistribution(object):
be empty.
* ``root_dir`` (string): The absolute path to the root directory to use
- to find distro-related information files.
+ to find distro-related information files. Note that ``include_*``
+ parameters must not be enabled in combination with ``root_dir``.
+
+ * ``include_oslevel`` (bool): Controls whether (AIX) oslevel command
+ output is included as a data source. If the oslevel command is not
+ available in the program execution path the data source will be
+ empty.
Public instance attributes:
@@ -718,14 +733,21 @@ class LinuxDistribution(object):
parameter. This controls whether the uname information will
be loaded.
+ * ``include_oslevel`` (bool): The result of the ``include_oslevel``
+ parameter. This controls whether (AIX) oslevel information will be
+ loaded.
+
+ * ``root_dir`` (string): The result of the ``root_dir`` parameter.
+ The absolute path to the root directory to use to find distro-related
+ information files.
+
Raises:
- * :py:exc:`IOError`: Some I/O issue with an os-release file or distro
- release file.
+ * :py:exc:`ValueError`: Initialization parameters combination is not
+ supported.
- * :py:exc:`subprocess.CalledProcessError`: The lsb_release command had
- some issue (other than not being available in the program execution
- path).
+ * :py:exc:`OSError`: Some I/O issue with an os-release file or distro
+ release file.
* :py:exc:`UnicodeError`: A data source has unexpected characters or
uses an unexpected encoding.
@@ -754,11 +776,24 @@ class LinuxDistribution(object):
self.os_release_file = usr_lib_os_release_file
self.distro_release_file = distro_release_file or "" # updated later
- self.include_lsb = include_lsb
- self.include_uname = include_uname
- def __repr__(self):
- # type: () -> str
+ is_root_dir_defined = root_dir is not None
+ if is_root_dir_defined and (include_lsb or include_uname or include_oslevel):
+ raise ValueError(
+ "Including subprocess data sources from specific root_dir is disallowed"
+ " to prevent false information"
+ )
+ self.include_lsb = (
+ include_lsb if include_lsb is not None else not is_root_dir_defined
+ )
+ self.include_uname = (
+ include_uname if include_uname is not None else not is_root_dir_defined
+ )
+ self.include_oslevel = (
+ include_oslevel if include_oslevel is not None else not is_root_dir_defined
+ )
+
+ def __repr__(self) -> str:
"""Return repr of all info"""
return (
"LinuxDistribution("
@@ -766,14 +801,18 @@ class LinuxDistribution(object):
"distro_release_file={self.distro_release_file!r}, "
"include_lsb={self.include_lsb!r}, "
"include_uname={self.include_uname!r}, "
+ "include_oslevel={self.include_oslevel!r}, "
+ "root_dir={self.root_dir!r}, "
"_os_release_info={self._os_release_info!r}, "
"_lsb_release_info={self._lsb_release_info!r}, "
"_distro_release_info={self._distro_release_info!r}, "
- "_uname_info={self._uname_info!r})".format(self=self)
+ "_uname_info={self._uname_info!r}, "
+ "_oslevel_info={self._oslevel_info!r})".format(self=self)
)
- def linux_distribution(self, full_distribution_name=True):
- # type: (bool) -> Tuple[str, str, str]
+ def linux_distribution(
+ self, full_distribution_name: bool = True
+ ) -> Tuple[str, str, str]:
"""
Return information about the OS distribution that is compatible
with Python's :func:`platform.linux_distribution`, supporting a subset
@@ -784,18 +823,16 @@ class LinuxDistribution(object):
return (
self.name() if full_distribution_name else self.id(),
self.version(),
- self.codename(),
+ self._os_release_info.get("release_codename") or self.codename(),
)
- def id(self):
- # type: () -> str
+ def id(self) -> str:
"""Return the distro ID of the OS distribution, as a string.
For details, see :func:`distro.id`.
"""
- def normalize(distro_id, table):
- # type: (str, Dict[str, str]) -> str
+ def normalize(distro_id: str, table: Dict[str, str]) -> str:
distro_id = distro_id.lower().replace(" ", "_")
return table.get(distro_id, distro_id)
@@ -817,8 +854,7 @@ class LinuxDistribution(object):
return ""
- def name(self, pretty=False):
- # type: (bool) -> str
+ def name(self, pretty: bool = False) -> str:
"""
Return the name of the OS distribution, as a string.
@@ -838,11 +874,10 @@ class LinuxDistribution(object):
name = self.distro_release_attr("name") or self.uname_attr("name")
version = self.version(pretty=True)
if version:
- name = name + " " + version
+ name = f"{name} {version}"
return name or ""
- def version(self, pretty=False, best=False):
- # type: (bool, bool) -> str
+ def version(self, pretty: bool = False, best: bool = False) -> str:
"""
Return the version of the OS distribution, as a string.
@@ -860,6 +895,12 @@ class LinuxDistribution(object):
).get("version_id", ""),
self.uname_attr("release"),
]
+ if self.uname_attr("id").startswith("aix"):
+ # On AIX platforms, prefer oslevel command output.
+ versions.insert(0, self.oslevel_info())
+ elif self.id() == "debian" or "debian" in self.like().split():
+ # On Debian-like, add debian_version file content to candidates list.
+ versions.append(self._debian_version)
version = ""
if best:
# This algorithm uses the last version in priority order that has
@@ -875,11 +916,10 @@ class LinuxDistribution(object):
version = v
break
if pretty and version and self.codename():
- version = "{0} ({1})".format(version, self.codename())
+ version = f"{version} ({self.codename()})"
return version
- def version_parts(self, best=False):
- # type: (bool) -> Tuple[str, str, str]
+ def version_parts(self, best: bool = False) -> Tuple[str, str, str]:
"""
Return the version of the OS distribution, as a tuple of version
numbers.
@@ -895,8 +935,7 @@ class LinuxDistribution(object):
return major, minor or "", build_number or ""
return "", "", ""
- def major_version(self, best=False):
- # type: (bool) -> str
+ def major_version(self, best: bool = False) -> str:
"""
Return the major version number of the current distribution.
@@ -904,8 +943,7 @@ class LinuxDistribution(object):
"""
return self.version_parts(best)[0]
- def minor_version(self, best=False):
- # type: (bool) -> str
+ def minor_version(self, best: bool = False) -> str:
"""
Return the minor version number of the current distribution.
@@ -913,8 +951,7 @@ class LinuxDistribution(object):
"""
return self.version_parts(best)[1]
- def build_number(self, best=False):
- # type: (bool) -> str
+ def build_number(self, best: bool = False) -> str:
"""
Return the build number of the current distribution.
@@ -922,8 +959,7 @@ class LinuxDistribution(object):
"""
return self.version_parts(best)[2]
- def like(self):
- # type: () -> str
+ def like(self) -> str:
"""
Return the IDs of distributions that are like the OS distribution.
@@ -931,8 +967,7 @@ class LinuxDistribution(object):
"""
return self.os_release_attr("id_like") or ""
- def codename(self):
- # type: () -> str
+ def codename(self) -> str:
"""
Return the codename of the OS distribution.
@@ -949,8 +984,7 @@ class LinuxDistribution(object):
or ""
)
- def info(self, pretty=False, best=False):
- # type: (bool, bool) -> InfoDict
+ def info(self, pretty: bool = False, best: bool = False) -> InfoDict:
"""
Return certain machine-readable information about the OS
distribution.
@@ -969,8 +1003,7 @@ class LinuxDistribution(object):
codename=self.codename(),
)
- def os_release_info(self):
- # type: () -> Dict[str, str]
+ def os_release_info(self) -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information
items from the os-release file data source of the OS distribution.
@@ -979,8 +1012,7 @@ class LinuxDistribution(object):
"""
return self._os_release_info
- def lsb_release_info(self):
- # type: () -> Dict[str, str]
+ def lsb_release_info(self) -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information
items from the lsb_release command data source of the OS
@@ -990,8 +1022,7 @@ class LinuxDistribution(object):
"""
return self._lsb_release_info
- def distro_release_info(self):
- # type: () -> Dict[str, str]
+ def distro_release_info(self) -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information
items from the distro release file data source of the OS
@@ -1001,8 +1032,7 @@ class LinuxDistribution(object):
"""
return self._distro_release_info
- def uname_info(self):
- # type: () -> Dict[str, str]
+ def uname_info(self) -> Dict[str, str]:
"""
Return a dictionary containing key-value pairs for the information
items from the uname command data source of the OS distribution.
@@ -1011,8 +1041,13 @@ class LinuxDistribution(object):
"""
return self._uname_info
- def os_release_attr(self, attribute):
- # type: (str) -> str
+ def oslevel_info(self) -> str:
+ """
+ Return AIX' oslevel command output.
+ """
+ return self._oslevel_info
+
+ def os_release_attr(self, attribute: str) -> str:
"""
Return a single named information item from the os-release file data
source of the OS distribution.
@@ -1021,8 +1056,7 @@ class LinuxDistribution(object):
"""
return self._os_release_info.get(attribute, "")
- def lsb_release_attr(self, attribute):
- # type: (str) -> str
+ def lsb_release_attr(self, attribute: str) -> str:
"""
Return a single named information item from the lsb_release command
output data source of the OS distribution.
@@ -1031,8 +1065,7 @@ class LinuxDistribution(object):
"""
return self._lsb_release_info.get(attribute, "")
- def distro_release_attr(self, attribute):
- # type: (str) -> str
+ def distro_release_attr(self, attribute: str) -> str:
"""
Return a single named information item from the distro release file
data source of the OS distribution.
@@ -1041,8 +1074,7 @@ class LinuxDistribution(object):
"""
return self._distro_release_info.get(attribute, "")
- def uname_attr(self, attribute):
- # type: (str) -> str
+ def uname_attr(self, attribute: str) -> str:
"""
Return a single named information item from the uname command
output data source of the OS distribution.
@@ -1052,8 +1084,7 @@ class LinuxDistribution(object):
return self._uname_info.get(attribute, "")
@cached_property
- def _os_release_info(self):
- # type: () -> Dict[str, str]
+ def _os_release_info(self) -> Dict[str, str]:
"""
Get the information items from the specified os-release file.
@@ -1061,13 +1092,12 @@ class LinuxDistribution(object):
A dictionary containing all information items.
"""
if os.path.isfile(self.os_release_file):
- with open(self.os_release_file) as release_file:
+ with open(self.os_release_file, encoding="utf-8") as release_file:
return self._parse_os_release_content(release_file)
return {}
@staticmethod
- def _parse_os_release_content(lines):
- # type: (TextIO) -> Dict[str, str]
+ def _parse_os_release_content(lines: TextIO) -> Dict[str, str]:
"""
Parse the lines of an os-release file.
@@ -1084,16 +1114,6 @@ class LinuxDistribution(object):
lexer = shlex.shlex(lines, posix=True)
lexer.whitespace_split = True
- # The shlex module defines its `wordchars` variable using literals,
- # making it dependent on the encoding of the Python source file.
- # In Python 2.6 and 2.7, the shlex source file is encoded in
- # 'iso-8859-1', and the `wordchars` variable is defined as a byte
- # string. This causes a UnicodeDecodeError to be raised when the
- # parsed content is a unicode object. The following fix resolves that
- # (... but it should be fixed in shlex...):
- if sys.version_info[0] == 2 and isinstance(lexer.wordchars, bytes):
- lexer.wordchars = lexer.wordchars.decode("iso-8859-1")
-
tokens = list(lexer)
for token in tokens:
# At this point, all shell-like parsing has been done (i.e.
@@ -1102,12 +1122,17 @@ class LinuxDistribution(object):
# stripped, etc.), so the tokens are now either:
# * variable assignments: var=value
# * commands or their arguments (not allowed in os-release)
+ # Ignore any tokens that are not variable assignments
if "=" in token:
k, v = token.split("=", 1)
props[k.lower()] = v
- else:
- # Ignore any tokens that are not variable assignments
- pass
+
+ if "version" in props:
+ # extract release codename (if any) from version attribute
+ match = re.search(r"\((\D+)\)|,\s*(\D+)", props["version"])
+ if match:
+ release_codename = match.group(1) or match.group(2)
+ props["codename"] = props["release_codename"] = release_codename
if "version_codename" in props:
# os-release added a version_codename field. Use that in
@@ -1118,22 +1143,11 @@ class LinuxDistribution(object):
elif "ubuntu_codename" in props:
# Same as above but a non-standard field name used on older Ubuntus
props["codename"] = props["ubuntu_codename"]
- elif "version" in props:
- # If there is no version_codename, parse it from the version
- match = re.search(r"(\(\D+\))|,(\s+)?\D+", props["version"])
- if match:
- codename = match.group()
- codename = codename.strip("()")
- codename = codename.strip(",")
- codename = codename.strip()
- # codename appears within paranthese.
- props["codename"] = codename
return props
@cached_property
- def _lsb_release_info(self):
- # type: () -> Dict[str, str]
+ def _lsb_release_info(self) -> Dict[str, str]:
"""
Get the information items from the lsb_release command output.
@@ -1142,19 +1156,17 @@ class LinuxDistribution(object):
"""
if not self.include_lsb:
return {}
- with open(os.devnull, "wb") as devnull:
- try:
- cmd = ("lsb_release", "-a")
- stdout = subprocess.check_output(cmd, stderr=devnull)
- # Command not found or lsb_release returned error
- except (OSError, subprocess.CalledProcessError):
- return {}
+ try:
+ cmd = ("lsb_release", "-a")
+ stdout = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
+ # Command not found or lsb_release returned error
+ except (OSError, subprocess.CalledProcessError):
+ return {}
content = self._to_str(stdout).splitlines()
return self._parse_lsb_release_content(content)
@staticmethod
- def _parse_lsb_release_content(lines):
- # type: (Iterable[str]) -> Dict[str, str]
+ def _parse_lsb_release_content(lines: Iterable[str]) -> Dict[str, str]:
"""
Parse the output of the lsb_release command.
@@ -1178,20 +1190,41 @@ class LinuxDistribution(object):
return props
@cached_property
- def _uname_info(self):
- # type: () -> Dict[str, str]
- with open(os.devnull, "wb") as devnull:
- try:
- cmd = ("uname", "-rs")
- stdout = subprocess.check_output(cmd, stderr=devnull)
- except OSError:
- return {}
+ def _uname_info(self) -> Dict[str, str]:
+ if not self.include_uname:
+ return {}
+ try:
+ cmd = ("uname", "-rs")
+ stdout = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
+ except OSError:
+ return {}
content = self._to_str(stdout).splitlines()
return self._parse_uname_content(content)
+ @cached_property
+ def _oslevel_info(self) -> str:
+ if not self.include_oslevel:
+ return ""
+ try:
+ stdout = subprocess.check_output("oslevel", stderr=subprocess.DEVNULL)
+ except (OSError, subprocess.CalledProcessError):
+ return ""
+ return self._to_str(stdout).strip()
+
+ @cached_property
+ def _debian_version(self) -> str:
+ try:
+ with open(
+ os.path.join(self.etc_dir, "debian_version"), encoding="ascii"
+ ) as fp:
+ return fp.readline().rstrip()
+ except FileNotFoundError:
+ return ""
+
@staticmethod
- def _parse_uname_content(lines):
- # type: (Sequence[str]) -> Dict[str, str]
+ def _parse_uname_content(lines: Sequence[str]) -> Dict[str, str]:
+ if not lines:
+ return {}
props = {}
match = re.search(r"^([^\s]+)\s+([\d\.]+)", lines[0].strip())
if match:
@@ -1208,23 +1241,12 @@ class LinuxDistribution(object):
return props
@staticmethod
- def _to_str(text):
- # type: (Union[bytes, str]) -> str
+ def _to_str(bytestring: bytes) -> str:
encoding = sys.getfilesystemencoding()
- encoding = "utf-8" if encoding == "ascii" else encoding
-
- if sys.version_info[0] >= 3:
- if isinstance(text, bytes):
- return text.decode(encoding)
- else:
- if isinstance(text, unicode): # noqa
- return text.encode(encoding)
-
- return text
+ return bytestring.decode(encoding)
@cached_property
- def _distro_release_info(self):
- # type: () -> Dict[str, str]
+ def _distro_release_info(self) -> Dict[str, str]:
"""
Get the information items from the specified distro release file.
@@ -1241,14 +1263,14 @@ class LinuxDistribution(object):
# file), because we want to use what was specified as best as
# possible.
match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
- if "name" in distro_info and "cloudlinux" in distro_info["name"].lower():
- distro_info["id"] = "cloudlinux"
- elif match:
- distro_info["id"] = match.group(1)
- return distro_info
else:
try:
- basenames = os.listdir(self.etc_dir)
+ basenames = [
+ basename
+ for basename in os.listdir(self.etc_dir)
+ if basename not in _DISTRO_RELEASE_IGNORE_BASENAMES
+ and os.path.isfile(os.path.join(self.etc_dir, basename))
+ ]
# We sort for repeatability in cases where there are multiple
# distro specific files; e.g. CentOS, Oracle, Enterprise all
# containing `redhat-release` on top of their own.
@@ -1258,41 +1280,31 @@ class LinuxDistribution(object):
# sure about the *-release files. Check common entries of
# /etc for information. If they turn out to not be there the
# error is handled in `_parse_distro_release_file()`.
- basenames = [
- "SuSE-release",
- "arch-release",
- "base-release",
- "centos-release",
- "fedora-release",
- "gentoo-release",
- "mageia-release",
- "mandrake-release",
- "mandriva-release",
- "mandrivalinux-release",
- "manjaro-release",
- "oracle-release",
- "redhat-release",
- "sl-release",
- "slackware-version",
- ]
+ basenames = _DISTRO_RELEASE_BASENAMES
for basename in basenames:
- if basename in _DISTRO_RELEASE_IGNORE_BASENAMES:
- continue
match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
- if match:
- filepath = os.path.join(self.etc_dir, basename)
- distro_info = self._parse_distro_release_file(filepath)
- if "name" in distro_info:
- # The name is always present if the pattern matches
- self.distro_release_file = filepath
- distro_info["id"] = match.group(1)
- if "cloudlinux" in distro_info["name"].lower():
- distro_info["id"] = "cloudlinux"
- return distro_info
- return {}
+ if match is None:
+ continue
+ filepath = os.path.join(self.etc_dir, basename)
+ distro_info = self._parse_distro_release_file(filepath)
+ # The name is always present if the pattern matches.
+ if "name" not in distro_info:
+ continue
+ self.distro_release_file = filepath
+ break
+ else: # the loop didn't "break": no candidate.
+ return {}
+
+ if match is not None:
+ distro_info["id"] = match.group(1)
+
+ # CloudLinux < 7: manually enrich info with proper id.
+ if "cloudlinux" in distro_info.get("name", "").lower():
+ distro_info["id"] = "cloudlinux"
+
+ return distro_info
- def _parse_distro_release_file(self, filepath):
- # type: (str) -> Dict[str, str]
+ def _parse_distro_release_file(self, filepath: str) -> Dict[str, str]:
"""
Parse a distro release file.
@@ -1304,19 +1316,18 @@ class LinuxDistribution(object):
A dictionary containing all information items.
"""
try:
- with open(filepath) as fp:
+ with open(filepath, encoding="utf-8") as fp:
# Only parse the first line. For instance, on SLES there
# are multiple lines. We don't want them...
return self._parse_distro_release_content(fp.readline())
- except (OSError, IOError):
+ except OSError:
# Ignore not being able to read a specific, seemingly version
# related file.
# See https://github.com/python-distro/distro/issues/162
return {}
@staticmethod
- def _parse_distro_release_content(line):
- # type: (str) -> Dict[str, str]
+ def _parse_distro_release_content(line: str) -> Dict[str, str]:
"""
Parse a line from a distro release file.
@@ -1344,8 +1355,7 @@ class LinuxDistribution(object):
_distro = LinuxDistribution()
-def main():
- # type: () -> None
+def main() -> None:
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler(sys.stdout))
@@ -1367,7 +1377,10 @@ def main():
if args.root_dir:
dist = LinuxDistribution(
- include_lsb=False, include_uname=False, root_dir=args.root_dir
+ include_lsb=False,
+ include_uname=False,
+ include_oslevel=False,
+ root_dir=args.root_dir,
)
else:
dist = _distro
diff --git a/src/pip/_vendor/distro/py.typed b/src/pip/_vendor/distro/py.typed
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/pip/_vendor/distro/py.typed
diff --git a/src/pip/_vendor/html5lib.pyi b/src/pip/_vendor/html5lib.pyi
deleted file mode 100644
index 9bc9af95e..000000000
--- a/src/pip/_vendor/html5lib.pyi
+++ /dev/null
@@ -1 +0,0 @@
-from html5lib import * \ No newline at end of file
diff --git a/src/pip/_vendor/html5lib/LICENSE b/src/pip/_vendor/html5lib/LICENSE
deleted file mode 100644
index c87fa7a00..000000000
--- a/src/pip/_vendor/html5lib/LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-Copyright (c) 2006-2013 James Graham and other contributors
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/src/pip/_vendor/html5lib/__init__.py b/src/pip/_vendor/html5lib/__init__.py
deleted file mode 100644
index d1d82f157..000000000
--- a/src/pip/_vendor/html5lib/__init__.py
+++ /dev/null
@@ -1,35 +0,0 @@
-"""
-HTML parsing library based on the `WHATWG HTML specification
-<https://whatwg.org/html>`_. The parser is designed to be compatible with
-existing HTML found in the wild and implements well-defined error recovery that
-is largely compatible with modern desktop web browsers.
-
-Example usage::
-
- from pip._vendor import html5lib
- with open("my_document.html", "rb") as f:
- tree = html5lib.parse(f)
-
-For convenience, this module re-exports the following names:
-
-* :func:`~.html5parser.parse`
-* :func:`~.html5parser.parseFragment`
-* :class:`~.html5parser.HTMLParser`
-* :func:`~.treebuilders.getTreeBuilder`
-* :func:`~.treewalkers.getTreeWalker`
-* :func:`~.serializer.serialize`
-"""
-
-from __future__ import absolute_import, division, unicode_literals
-
-from .html5parser import HTMLParser, parse, parseFragment
-from .treebuilders import getTreeBuilder
-from .treewalkers import getTreeWalker
-from .serializer import serialize
-
-__all__ = ["HTMLParser", "parse", "parseFragment", "getTreeBuilder",
- "getTreeWalker", "serialize"]
-
-# this has to be at the top level, see how setup.py parses this
-#: Distribution version number.
-__version__ = "1.1"
diff --git a/src/pip/_vendor/html5lib/_ihatexml.py b/src/pip/_vendor/html5lib/_ihatexml.py
deleted file mode 100644
index 3ff803c19..000000000
--- a/src/pip/_vendor/html5lib/_ihatexml.py
+++ /dev/null
@@ -1,289 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-import re
-import warnings
-
-from .constants import DataLossWarning
-
-baseChar = """
-[#x0041-#x005A] | [#x0061-#x007A] | [#x00C0-#x00D6] | [#x00D8-#x00F6] |
-[#x00F8-#x00FF] | [#x0100-#x0131] | [#x0134-#x013E] | [#x0141-#x0148] |
-[#x014A-#x017E] | [#x0180-#x01C3] | [#x01CD-#x01F0] | [#x01F4-#x01F5] |
-[#x01FA-#x0217] | [#x0250-#x02A8] | [#x02BB-#x02C1] | #x0386 |
-[#x0388-#x038A] | #x038C | [#x038E-#x03A1] | [#x03A3-#x03CE] |
-[#x03D0-#x03D6] | #x03DA | #x03DC | #x03DE | #x03E0 | [#x03E2-#x03F3] |
-[#x0401-#x040C] | [#x040E-#x044F] | [#x0451-#x045C] | [#x045E-#x0481] |
-[#x0490-#x04C4] | [#x04C7-#x04C8] | [#x04CB-#x04CC] | [#x04D0-#x04EB] |
-[#x04EE-#x04F5] | [#x04F8-#x04F9] | [#x0531-#x0556] | #x0559 |
-[#x0561-#x0586] | [#x05D0-#x05EA] | [#x05F0-#x05F2] | [#x0621-#x063A] |
-[#x0641-#x064A] | [#x0671-#x06B7] | [#x06BA-#x06BE] | [#x06C0-#x06CE] |
-[#x06D0-#x06D3] | #x06D5 | [#x06E5-#x06E6] | [#x0905-#x0939] | #x093D |
-[#x0958-#x0961] | [#x0985-#x098C] | [#x098F-#x0990] | [#x0993-#x09A8] |
-[#x09AA-#x09B0] | #x09B2 | [#x09B6-#x09B9] | [#x09DC-#x09DD] |
-[#x09DF-#x09E1] | [#x09F0-#x09F1] | [#x0A05-#x0A0A] | [#x0A0F-#x0A10] |
-[#x0A13-#x0A28] | [#x0A2A-#x0A30] | [#x0A32-#x0A33] | [#x0A35-#x0A36] |
-[#x0A38-#x0A39] | [#x0A59-#x0A5C] | #x0A5E | [#x0A72-#x0A74] |
-[#x0A85-#x0A8B] | #x0A8D | [#x0A8F-#x0A91] | [#x0A93-#x0AA8] |
-[#x0AAA-#x0AB0] | [#x0AB2-#x0AB3] | [#x0AB5-#x0AB9] | #x0ABD | #x0AE0 |
-[#x0B05-#x0B0C] | [#x0B0F-#x0B10] | [#x0B13-#x0B28] | [#x0B2A-#x0B30] |
-[#x0B32-#x0B33] | [#x0B36-#x0B39] | #x0B3D | [#x0B5C-#x0B5D] |
-[#x0B5F-#x0B61] | [#x0B85-#x0B8A] | [#x0B8E-#x0B90] | [#x0B92-#x0B95] |
-[#x0B99-#x0B9A] | #x0B9C | [#x0B9E-#x0B9F] | [#x0BA3-#x0BA4] |
-[#x0BA8-#x0BAA] | [#x0BAE-#x0BB5] | [#x0BB7-#x0BB9] | [#x0C05-#x0C0C] |
-[#x0C0E-#x0C10] | [#x0C12-#x0C28] | [#x0C2A-#x0C33] | [#x0C35-#x0C39] |
-[#x0C60-#x0C61] | [#x0C85-#x0C8C] | [#x0C8E-#x0C90] | [#x0C92-#x0CA8] |
-[#x0CAA-#x0CB3] | [#x0CB5-#x0CB9] | #x0CDE | [#x0CE0-#x0CE1] |
-[#x0D05-#x0D0C] | [#x0D0E-#x0D10] | [#x0D12-#x0D28] | [#x0D2A-#x0D39] |
-[#x0D60-#x0D61] | [#x0E01-#x0E2E] | #x0E30 | [#x0E32-#x0E33] |
-[#x0E40-#x0E45] | [#x0E81-#x0E82] | #x0E84 | [#x0E87-#x0E88] | #x0E8A |
-#x0E8D | [#x0E94-#x0E97] | [#x0E99-#x0E9F] | [#x0EA1-#x0EA3] | #x0EA5 |
-#x0EA7 | [#x0EAA-#x0EAB] | [#x0EAD-#x0EAE] | #x0EB0 | [#x0EB2-#x0EB3] |
-#x0EBD | [#x0EC0-#x0EC4] | [#x0F40-#x0F47] | [#x0F49-#x0F69] |
-[#x10A0-#x10C5] | [#x10D0-#x10F6] | #x1100 | [#x1102-#x1103] |
-[#x1105-#x1107] | #x1109 | [#x110B-#x110C] | [#x110E-#x1112] | #x113C |
-#x113E | #x1140 | #x114C | #x114E | #x1150 | [#x1154-#x1155] | #x1159 |
-[#x115F-#x1161] | #x1163 | #x1165 | #x1167 | #x1169 | [#x116D-#x116E] |
-[#x1172-#x1173] | #x1175 | #x119E | #x11A8 | #x11AB | [#x11AE-#x11AF] |
-[#x11B7-#x11B8] | #x11BA | [#x11BC-#x11C2] | #x11EB | #x11F0 | #x11F9 |
-[#x1E00-#x1E9B] | [#x1EA0-#x1EF9] | [#x1F00-#x1F15] | [#x1F18-#x1F1D] |
-[#x1F20-#x1F45] | [#x1F48-#x1F4D] | [#x1F50-#x1F57] | #x1F59 | #x1F5B |
-#x1F5D | [#x1F5F-#x1F7D] | [#x1F80-#x1FB4] | [#x1FB6-#x1FBC] | #x1FBE |
-[#x1FC2-#x1FC4] | [#x1FC6-#x1FCC] | [#x1FD0-#x1FD3] | [#x1FD6-#x1FDB] |
-[#x1FE0-#x1FEC] | [#x1FF2-#x1FF4] | [#x1FF6-#x1FFC] | #x2126 |
-[#x212A-#x212B] | #x212E | [#x2180-#x2182] | [#x3041-#x3094] |
-[#x30A1-#x30FA] | [#x3105-#x312C] | [#xAC00-#xD7A3]"""
-
-ideographic = """[#x4E00-#x9FA5] | #x3007 | [#x3021-#x3029]"""
-
-combiningCharacter = """
-[#x0300-#x0345] | [#x0360-#x0361] | [#x0483-#x0486] | [#x0591-#x05A1] |
-[#x05A3-#x05B9] | [#x05BB-#x05BD] | #x05BF | [#x05C1-#x05C2] | #x05C4 |
-[#x064B-#x0652] | #x0670 | [#x06D6-#x06DC] | [#x06DD-#x06DF] |
-[#x06E0-#x06E4] | [#x06E7-#x06E8] | [#x06EA-#x06ED] | [#x0901-#x0903] |
-#x093C | [#x093E-#x094C] | #x094D | [#x0951-#x0954] | [#x0962-#x0963] |
-[#x0981-#x0983] | #x09BC | #x09BE | #x09BF | [#x09C0-#x09C4] |
-[#x09C7-#x09C8] | [#x09CB-#x09CD] | #x09D7 | [#x09E2-#x09E3] | #x0A02 |
-#x0A3C | #x0A3E | #x0A3F | [#x0A40-#x0A42] | [#x0A47-#x0A48] |
-[#x0A4B-#x0A4D] | [#x0A70-#x0A71] | [#x0A81-#x0A83] | #x0ABC |
-[#x0ABE-#x0AC5] | [#x0AC7-#x0AC9] | [#x0ACB-#x0ACD] | [#x0B01-#x0B03] |
-#x0B3C | [#x0B3E-#x0B43] | [#x0B47-#x0B48] | [#x0B4B-#x0B4D] |
-[#x0B56-#x0B57] | [#x0B82-#x0B83] | [#x0BBE-#x0BC2] | [#x0BC6-#x0BC8] |
-[#x0BCA-#x0BCD] | #x0BD7 | [#x0C01-#x0C03] | [#x0C3E-#x0C44] |
-[#x0C46-#x0C48] | [#x0C4A-#x0C4D] | [#x0C55-#x0C56] | [#x0C82-#x0C83] |
-[#x0CBE-#x0CC4] | [#x0CC6-#x0CC8] | [#x0CCA-#x0CCD] | [#x0CD5-#x0CD6] |
-[#x0D02-#x0D03] | [#x0D3E-#x0D43] | [#x0D46-#x0D48] | [#x0D4A-#x0D4D] |
-#x0D57 | #x0E31 | [#x0E34-#x0E3A] | [#x0E47-#x0E4E] | #x0EB1 |
-[#x0EB4-#x0EB9] | [#x0EBB-#x0EBC] | [#x0EC8-#x0ECD] | [#x0F18-#x0F19] |
-#x0F35 | #x0F37 | #x0F39 | #x0F3E | #x0F3F | [#x0F71-#x0F84] |
-[#x0F86-#x0F8B] | [#x0F90-#x0F95] | #x0F97 | [#x0F99-#x0FAD] |
-[#x0FB1-#x0FB7] | #x0FB9 | [#x20D0-#x20DC] | #x20E1 | [#x302A-#x302F] |
-#x3099 | #x309A"""
-
-digit = """
-[#x0030-#x0039] | [#x0660-#x0669] | [#x06F0-#x06F9] | [#x0966-#x096F] |
-[#x09E6-#x09EF] | [#x0A66-#x0A6F] | [#x0AE6-#x0AEF] | [#x0B66-#x0B6F] |
-[#x0BE7-#x0BEF] | [#x0C66-#x0C6F] | [#x0CE6-#x0CEF] | [#x0D66-#x0D6F] |
-[#x0E50-#x0E59] | [#x0ED0-#x0ED9] | [#x0F20-#x0F29]"""
-
-extender = """
-#x00B7 | #x02D0 | #x02D1 | #x0387 | #x0640 | #x0E46 | #x0EC6 | #x3005 |
-#[#x3031-#x3035] | [#x309D-#x309E] | [#x30FC-#x30FE]"""
-
-letter = " | ".join([baseChar, ideographic])
-
-# Without the
-name = " | ".join([letter, digit, ".", "-", "_", combiningCharacter,
- extender])
-nameFirst = " | ".join([letter, "_"])
-
-reChar = re.compile(r"#x([\d|A-F]{4,4})")
-reCharRange = re.compile(r"\[#x([\d|A-F]{4,4})-#x([\d|A-F]{4,4})\]")
-
-
-def charStringToList(chars):
- charRanges = [item.strip() for item in chars.split(" | ")]
- rv = []
- for item in charRanges:
- foundMatch = False
- for regexp in (reChar, reCharRange):
- match = regexp.match(item)
- if match is not None:
- rv.append([hexToInt(item) for item in match.groups()])
- if len(rv[-1]) == 1:
- rv[-1] = rv[-1] * 2
- foundMatch = True
- break
- if not foundMatch:
- assert len(item) == 1
-
- rv.append([ord(item)] * 2)
- rv = normaliseCharList(rv)
- return rv
-
-
-def normaliseCharList(charList):
- charList = sorted(charList)
- for item in charList:
- assert item[1] >= item[0]
- rv = []
- i = 0
- while i < len(charList):
- j = 1
- rv.append(charList[i])
- while i + j < len(charList) and charList[i + j][0] <= rv[-1][1] + 1:
- rv[-1][1] = charList[i + j][1]
- j += 1
- i += j
- return rv
-
-
-# We don't really support characters above the BMP :(
-max_unicode = int("FFFF", 16)
-
-
-def missingRanges(charList):
- rv = []
- if charList[0] != 0:
- rv.append([0, charList[0][0] - 1])
- for i, item in enumerate(charList[:-1]):
- rv.append([item[1] + 1, charList[i + 1][0] - 1])
- if charList[-1][1] != max_unicode:
- rv.append([charList[-1][1] + 1, max_unicode])
- return rv
-
-
-def listToRegexpStr(charList):
- rv = []
- for item in charList:
- if item[0] == item[1]:
- rv.append(escapeRegexp(chr(item[0])))
- else:
- rv.append(escapeRegexp(chr(item[0])) + "-" +
- escapeRegexp(chr(item[1])))
- return "[%s]" % "".join(rv)
-
-
-def hexToInt(hex_str):
- return int(hex_str, 16)
-
-
-def escapeRegexp(string):
- specialCharacters = (".", "^", "$", "*", "+", "?", "{", "}",
- "[", "]", "|", "(", ")", "-")
- for char in specialCharacters:
- string = string.replace(char, "\\" + char)
-
- return string
-
-# output from the above
-nonXmlNameBMPRegexp = re.compile('[\x00-,/:-@\\[-\\^`\\{-\xb6\xb8-\xbf\xd7\xf7\u0132-\u0133\u013f-\u0140\u0149\u017f\u01c4-\u01cc\u01f1-\u01f3\u01f6-\u01f9\u0218-\u024f\u02a9-\u02ba\u02c2-\u02cf\u02d2-\u02ff\u0346-\u035f\u0362-\u0385\u038b\u038d\u03a2\u03cf\u03d7-\u03d9\u03db\u03dd\u03df\u03e1\u03f4-\u0400\u040d\u0450\u045d\u0482\u0487-\u048f\u04c5-\u04c6\u04c9-\u04ca\u04cd-\u04cf\u04ec-\u04ed\u04f6-\u04f7\u04fa-\u0530\u0557-\u0558\u055a-\u0560\u0587-\u0590\u05a2\u05ba\u05be\u05c0\u05c3\u05c5-\u05cf\u05eb-\u05ef\u05f3-\u0620\u063b-\u063f\u0653-\u065f\u066a-\u066f\u06b8-\u06b9\u06bf\u06cf\u06d4\u06e9\u06ee-\u06ef\u06fa-\u0900\u0904\u093a-\u093b\u094e-\u0950\u0955-\u0957\u0964-\u0965\u0970-\u0980\u0984\u098d-\u098e\u0991-\u0992\u09a9\u09b1\u09b3-\u09b5\u09ba-\u09bb\u09bd\u09c5-\u09c6\u09c9-\u09ca\u09ce-\u09d6\u09d8-\u09db\u09de\u09e4-\u09e5\u09f2-\u0a01\u0a03-\u0a04\u0a0b-\u0a0e\u0a11-\u0a12\u0a29\u0a31\u0a34\u0a37\u0a3a-\u0a3b\u0a3d\u0a43-\u0a46\u0a49-\u0a4a\u0a4e-\u0a58\u0a5d\u0a5f-\u0a65\u0a75-\u0a80\u0a84\u0a8c\u0a8e\u0a92\u0aa9\u0ab1\u0ab4\u0aba-\u0abb\u0ac6\u0aca\u0ace-\u0adf\u0ae1-\u0ae5\u0af0-\u0b00\u0b04\u0b0d-\u0b0e\u0b11-\u0b12\u0b29\u0b31\u0b34-\u0b35\u0b3a-\u0b3b\u0b44-\u0b46\u0b49-\u0b4a\u0b4e-\u0b55\u0b58-\u0b5b\u0b5e\u0b62-\u0b65\u0b70-\u0b81\u0b84\u0b8b-\u0b8d\u0b91\u0b96-\u0b98\u0b9b\u0b9d\u0ba0-\u0ba2\u0ba5-\u0ba7\u0bab-\u0bad\u0bb6\u0bba-\u0bbd\u0bc3-\u0bc5\u0bc9\u0bce-\u0bd6\u0bd8-\u0be6\u0bf0-\u0c00\u0c04\u0c0d\u0c11\u0c29\u0c34\u0c3a-\u0c3d\u0c45\u0c49\u0c4e-\u0c54\u0c57-\u0c5f\u0c62-\u0c65\u0c70-\u0c81\u0c84\u0c8d\u0c91\u0ca9\u0cb4\u0cba-\u0cbd\u0cc5\u0cc9\u0cce-\u0cd4\u0cd7-\u0cdd\u0cdf\u0ce2-\u0ce5\u0cf0-\u0d01\u0d04\u0d0d\u0d11\u0d29\u0d3a-\u0d3d\u0d44-\u0d45\u0d49\u0d4e-\u0d56\u0d58-\u0d5f\u0d62-\u0d65\u0d70-\u0e00\u0e2f\u0e3b-\u0e3f\u0e4f\u0e5a-\u0e80\u0e83\u0e85-\u0e86\u0e89\u0e8b-\u0e8c\u0e8e-\u0e93\u0e98\u0ea0\u0ea4\u0ea6\u0ea8-\u0ea9\u0eac\u0eaf\u0eba\u0ebe-\u0ebf\u0ec5\u0ec7\u0ece-\u0ecf\u0eda-\u0f17\u0f1a-\u0f1f\u0f2a-\u0f34\u0f36\u0f38\u0f3a-\u0f3d\u0f48\u0f6a-\u0f70\u0f85\u0f8c-\u0f8f\u0f96\u0f98\u0fae-\u0fb0\u0fb8\u0fba-\u109f\u10c6-\u10cf\u10f7-\u10ff\u1101\u1104\u1108\u110a\u110d\u1113-\u113b\u113d\u113f\u1141-\u114b\u114d\u114f\u1151-\u1153\u1156-\u1158\u115a-\u115e\u1162\u1164\u1166\u1168\u116a-\u116c\u116f-\u1171\u1174\u1176-\u119d\u119f-\u11a7\u11a9-\u11aa\u11ac-\u11ad\u11b0-\u11b6\u11b9\u11bb\u11c3-\u11ea\u11ec-\u11ef\u11f1-\u11f8\u11fa-\u1dff\u1e9c-\u1e9f\u1efa-\u1eff\u1f16-\u1f17\u1f1e-\u1f1f\u1f46-\u1f47\u1f4e-\u1f4f\u1f58\u1f5a\u1f5c\u1f5e\u1f7e-\u1f7f\u1fb5\u1fbd\u1fbf-\u1fc1\u1fc5\u1fcd-\u1fcf\u1fd4-\u1fd5\u1fdc-\u1fdf\u1fed-\u1ff1\u1ff5\u1ffd-\u20cf\u20dd-\u20e0\u20e2-\u2125\u2127-\u2129\u212c-\u212d\u212f-\u217f\u2183-\u3004\u3006\u3008-\u3020\u3030\u3036-\u3040\u3095-\u3098\u309b-\u309c\u309f-\u30a0\u30fb\u30ff-\u3104\u312d-\u4dff\u9fa6-\uabff\ud7a4-\uffff]') # noqa
-
-nonXmlNameFirstBMPRegexp = re.compile('[\x00-@\\[-\\^`\\{-\xbf\xd7\xf7\u0132-\u0133\u013f-\u0140\u0149\u017f\u01c4-\u01cc\u01f1-\u01f3\u01f6-\u01f9\u0218-\u024f\u02a9-\u02ba\u02c2-\u0385\u0387\u038b\u038d\u03a2\u03cf\u03d7-\u03d9\u03db\u03dd\u03df\u03e1\u03f4-\u0400\u040d\u0450\u045d\u0482-\u048f\u04c5-\u04c6\u04c9-\u04ca\u04cd-\u04cf\u04ec-\u04ed\u04f6-\u04f7\u04fa-\u0530\u0557-\u0558\u055a-\u0560\u0587-\u05cf\u05eb-\u05ef\u05f3-\u0620\u063b-\u0640\u064b-\u0670\u06b8-\u06b9\u06bf\u06cf\u06d4\u06d6-\u06e4\u06e7-\u0904\u093a-\u093c\u093e-\u0957\u0962-\u0984\u098d-\u098e\u0991-\u0992\u09a9\u09b1\u09b3-\u09b5\u09ba-\u09db\u09de\u09e2-\u09ef\u09f2-\u0a04\u0a0b-\u0a0e\u0a11-\u0a12\u0a29\u0a31\u0a34\u0a37\u0a3a-\u0a58\u0a5d\u0a5f-\u0a71\u0a75-\u0a84\u0a8c\u0a8e\u0a92\u0aa9\u0ab1\u0ab4\u0aba-\u0abc\u0abe-\u0adf\u0ae1-\u0b04\u0b0d-\u0b0e\u0b11-\u0b12\u0b29\u0b31\u0b34-\u0b35\u0b3a-\u0b3c\u0b3e-\u0b5b\u0b5e\u0b62-\u0b84\u0b8b-\u0b8d\u0b91\u0b96-\u0b98\u0b9b\u0b9d\u0ba0-\u0ba2\u0ba5-\u0ba7\u0bab-\u0bad\u0bb6\u0bba-\u0c04\u0c0d\u0c11\u0c29\u0c34\u0c3a-\u0c5f\u0c62-\u0c84\u0c8d\u0c91\u0ca9\u0cb4\u0cba-\u0cdd\u0cdf\u0ce2-\u0d04\u0d0d\u0d11\u0d29\u0d3a-\u0d5f\u0d62-\u0e00\u0e2f\u0e31\u0e34-\u0e3f\u0e46-\u0e80\u0e83\u0e85-\u0e86\u0e89\u0e8b-\u0e8c\u0e8e-\u0e93\u0e98\u0ea0\u0ea4\u0ea6\u0ea8-\u0ea9\u0eac\u0eaf\u0eb1\u0eb4-\u0ebc\u0ebe-\u0ebf\u0ec5-\u0f3f\u0f48\u0f6a-\u109f\u10c6-\u10cf\u10f7-\u10ff\u1101\u1104\u1108\u110a\u110d\u1113-\u113b\u113d\u113f\u1141-\u114b\u114d\u114f\u1151-\u1153\u1156-\u1158\u115a-\u115e\u1162\u1164\u1166\u1168\u116a-\u116c\u116f-\u1171\u1174\u1176-\u119d\u119f-\u11a7\u11a9-\u11aa\u11ac-\u11ad\u11b0-\u11b6\u11b9\u11bb\u11c3-\u11ea\u11ec-\u11ef\u11f1-\u11f8\u11fa-\u1dff\u1e9c-\u1e9f\u1efa-\u1eff\u1f16-\u1f17\u1f1e-\u1f1f\u1f46-\u1f47\u1f4e-\u1f4f\u1f58\u1f5a\u1f5c\u1f5e\u1f7e-\u1f7f\u1fb5\u1fbd\u1fbf-\u1fc1\u1fc5\u1fcd-\u1fcf\u1fd4-\u1fd5\u1fdc-\u1fdf\u1fed-\u1ff1\u1ff5\u1ffd-\u2125\u2127-\u2129\u212c-\u212d\u212f-\u217f\u2183-\u3006\u3008-\u3020\u302a-\u3040\u3095-\u30a0\u30fb-\u3104\u312d-\u4dff\u9fa6-\uabff\ud7a4-\uffff]') # noqa
-
-# Simpler things
-nonPubidCharRegexp = re.compile("[^\x20\x0D\x0Aa-zA-Z0-9\\-'()+,./:=?;!*#@$_%]")
-
-
-class InfosetFilter(object):
- replacementRegexp = re.compile(r"U[\dA-F]{5,5}")
-
- def __init__(self,
- dropXmlnsLocalName=False,
- dropXmlnsAttrNs=False,
- preventDoubleDashComments=False,
- preventDashAtCommentEnd=False,
- replaceFormFeedCharacters=True,
- preventSingleQuotePubid=False):
-
- self.dropXmlnsLocalName = dropXmlnsLocalName
- self.dropXmlnsAttrNs = dropXmlnsAttrNs
-
- self.preventDoubleDashComments = preventDoubleDashComments
- self.preventDashAtCommentEnd = preventDashAtCommentEnd
-
- self.replaceFormFeedCharacters = replaceFormFeedCharacters
-
- self.preventSingleQuotePubid = preventSingleQuotePubid
-
- self.replaceCache = {}
-
- def coerceAttribute(self, name, namespace=None):
- if self.dropXmlnsLocalName and name.startswith("xmlns:"):
- warnings.warn("Attributes cannot begin with xmlns", DataLossWarning)
- return None
- elif (self.dropXmlnsAttrNs and
- namespace == "http://www.w3.org/2000/xmlns/"):
- warnings.warn("Attributes cannot be in the xml namespace", DataLossWarning)
- return None
- else:
- return self.toXmlName(name)
-
- def coerceElement(self, name):
- return self.toXmlName(name)
-
- def coerceComment(self, data):
- if self.preventDoubleDashComments:
- while "--" in data:
- warnings.warn("Comments cannot contain adjacent dashes", DataLossWarning)
- data = data.replace("--", "- -")
- if data.endswith("-"):
- warnings.warn("Comments cannot end in a dash", DataLossWarning)
- data += " "
- return data
-
- def coerceCharacters(self, data):
- if self.replaceFormFeedCharacters:
- for _ in range(data.count("\x0C")):
- warnings.warn("Text cannot contain U+000C", DataLossWarning)
- data = data.replace("\x0C", " ")
- # Other non-xml characters
- return data
-
- def coercePubid(self, data):
- dataOutput = data
- for char in nonPubidCharRegexp.findall(data):
- warnings.warn("Coercing non-XML pubid", DataLossWarning)
- replacement = self.getReplacementCharacter(char)
- dataOutput = dataOutput.replace(char, replacement)
- if self.preventSingleQuotePubid and dataOutput.find("'") >= 0:
- warnings.warn("Pubid cannot contain single quote", DataLossWarning)
- dataOutput = dataOutput.replace("'", self.getReplacementCharacter("'"))
- return dataOutput
-
- def toXmlName(self, name):
- nameFirst = name[0]
- nameRest = name[1:]
- m = nonXmlNameFirstBMPRegexp.match(nameFirst)
- if m:
- warnings.warn("Coercing non-XML name: %s" % name, DataLossWarning)
- nameFirstOutput = self.getReplacementCharacter(nameFirst)
- else:
- nameFirstOutput = nameFirst
-
- nameRestOutput = nameRest
- replaceChars = set(nonXmlNameBMPRegexp.findall(nameRest))
- for char in replaceChars:
- warnings.warn("Coercing non-XML name: %s" % name, DataLossWarning)
- replacement = self.getReplacementCharacter(char)
- nameRestOutput = nameRestOutput.replace(char, replacement)
- return nameFirstOutput + nameRestOutput
-
- def getReplacementCharacter(self, char):
- if char in self.replaceCache:
- replacement = self.replaceCache[char]
- else:
- replacement = self.escapeChar(char)
- return replacement
-
- def fromXmlName(self, name):
- for item in set(self.replacementRegexp.findall(name)):
- name = name.replace(item, self.unescapeChar(item))
- return name
-
- def escapeChar(self, char):
- replacement = "U%05X" % ord(char)
- self.replaceCache[char] = replacement
- return replacement
-
- def unescapeChar(self, charcode):
- return chr(int(charcode[1:], 16))
diff --git a/src/pip/_vendor/html5lib/_inputstream.py b/src/pip/_vendor/html5lib/_inputstream.py
deleted file mode 100644
index e0bb37602..000000000
--- a/src/pip/_vendor/html5lib/_inputstream.py
+++ /dev/null
@@ -1,918 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-from pip._vendor.six import text_type
-from pip._vendor.six.moves import http_client, urllib
-
-import codecs
-import re
-from io import BytesIO, StringIO
-
-from pip._vendor import webencodings
-
-from .constants import EOF, spaceCharacters, asciiLetters, asciiUppercase
-from .constants import _ReparseException
-from . import _utils
-
-# Non-unicode versions of constants for use in the pre-parser
-spaceCharactersBytes = frozenset([item.encode("ascii") for item in spaceCharacters])
-asciiLettersBytes = frozenset([item.encode("ascii") for item in asciiLetters])
-asciiUppercaseBytes = frozenset([item.encode("ascii") for item in asciiUppercase])
-spacesAngleBrackets = spaceCharactersBytes | frozenset([b">", b"<"])
-
-
-invalid_unicode_no_surrogate = "[\u0001-\u0008\u000B\u000E-\u001F\u007F-\u009F\uFDD0-\uFDEF\uFFFE\uFFFF\U0001FFFE\U0001FFFF\U0002FFFE\U0002FFFF\U0003FFFE\U0003FFFF\U0004FFFE\U0004FFFF\U0005FFFE\U0005FFFF\U0006FFFE\U0006FFFF\U0007FFFE\U0007FFFF\U0008FFFE\U0008FFFF\U0009FFFE\U0009FFFF\U000AFFFE\U000AFFFF\U000BFFFE\U000BFFFF\U000CFFFE\U000CFFFF\U000DFFFE\U000DFFFF\U000EFFFE\U000EFFFF\U000FFFFE\U000FFFFF\U0010FFFE\U0010FFFF]" # noqa
-
-if _utils.supports_lone_surrogates:
- # Use one extra step of indirection and create surrogates with
- # eval. Not using this indirection would introduce an illegal
- # unicode literal on platforms not supporting such lone
- # surrogates.
- assert invalid_unicode_no_surrogate[-1] == "]" and invalid_unicode_no_surrogate.count("]") == 1
- invalid_unicode_re = re.compile(invalid_unicode_no_surrogate[:-1] +
- eval('"\\uD800-\\uDFFF"') + # pylint:disable=eval-used
- "]")
-else:
- invalid_unicode_re = re.compile(invalid_unicode_no_surrogate)
-
-non_bmp_invalid_codepoints = {0x1FFFE, 0x1FFFF, 0x2FFFE, 0x2FFFF, 0x3FFFE,
- 0x3FFFF, 0x4FFFE, 0x4FFFF, 0x5FFFE, 0x5FFFF,
- 0x6FFFE, 0x6FFFF, 0x7FFFE, 0x7FFFF, 0x8FFFE,
- 0x8FFFF, 0x9FFFE, 0x9FFFF, 0xAFFFE, 0xAFFFF,
- 0xBFFFE, 0xBFFFF, 0xCFFFE, 0xCFFFF, 0xDFFFE,
- 0xDFFFF, 0xEFFFE, 0xEFFFF, 0xFFFFE, 0xFFFFF,
- 0x10FFFE, 0x10FFFF}
-
-ascii_punctuation_re = re.compile("[\u0009-\u000D\u0020-\u002F\u003A-\u0040\u005C\u005B-\u0060\u007B-\u007E]")
-
-# Cache for charsUntil()
-charsUntilRegEx = {}
-
-
-class BufferedStream(object):
- """Buffering for streams that do not have buffering of their own
-
- The buffer is implemented as a list of chunks on the assumption that
- joining many strings will be slow since it is O(n**2)
- """
-
- def __init__(self, stream):
- self.stream = stream
- self.buffer = []
- self.position = [-1, 0] # chunk number, offset
-
- def tell(self):
- pos = 0
- for chunk in self.buffer[:self.position[0]]:
- pos += len(chunk)
- pos += self.position[1]
- return pos
-
- def seek(self, pos):
- assert pos <= self._bufferedBytes()
- offset = pos
- i = 0
- while len(self.buffer[i]) < offset:
- offset -= len(self.buffer[i])
- i += 1
- self.position = [i, offset]
-
- def read(self, bytes):
- if not self.buffer:
- return self._readStream(bytes)
- elif (self.position[0] == len(self.buffer) and
- self.position[1] == len(self.buffer[-1])):
- return self._readStream(bytes)
- else:
- return self._readFromBuffer(bytes)
-
- def _bufferedBytes(self):
- return sum([len(item) for item in self.buffer])
-
- def _readStream(self, bytes):
- data = self.stream.read(bytes)
- self.buffer.append(data)
- self.position[0] += 1
- self.position[1] = len(data)
- return data
-
- def _readFromBuffer(self, bytes):
- remainingBytes = bytes
- rv = []
- bufferIndex = self.position[0]
- bufferOffset = self.position[1]
- while bufferIndex < len(self.buffer) and remainingBytes != 0:
- assert remainingBytes > 0
- bufferedData = self.buffer[bufferIndex]
-
- if remainingBytes <= len(bufferedData) - bufferOffset:
- bytesToRead = remainingBytes
- self.position = [bufferIndex, bufferOffset + bytesToRead]
- else:
- bytesToRead = len(bufferedData) - bufferOffset
- self.position = [bufferIndex, len(bufferedData)]
- bufferIndex += 1
- rv.append(bufferedData[bufferOffset:bufferOffset + bytesToRead])
- remainingBytes -= bytesToRead
-
- bufferOffset = 0
-
- if remainingBytes:
- rv.append(self._readStream(remainingBytes))
-
- return b"".join(rv)
-
-
-def HTMLInputStream(source, **kwargs):
- # Work around Python bug #20007: read(0) closes the connection.
- # http://bugs.python.org/issue20007
- if (isinstance(source, http_client.HTTPResponse) or
- # Also check for addinfourl wrapping HTTPResponse
- (isinstance(source, urllib.response.addbase) and
- isinstance(source.fp, http_client.HTTPResponse))):
- isUnicode = False
- elif hasattr(source, "read"):
- isUnicode = isinstance(source.read(0), text_type)
- else:
- isUnicode = isinstance(source, text_type)
-
- if isUnicode:
- encodings = [x for x in kwargs if x.endswith("_encoding")]
- if encodings:
- raise TypeError("Cannot set an encoding with a unicode input, set %r" % encodings)
-
- return HTMLUnicodeInputStream(source, **kwargs)
- else:
- return HTMLBinaryInputStream(source, **kwargs)
-
-
-class HTMLUnicodeInputStream(object):
- """Provides a unicode stream of characters to the HTMLTokenizer.
-
- This class takes care of character encoding and removing or replacing
- incorrect byte-sequences and also provides column and line tracking.
-
- """
-
- _defaultChunkSize = 10240
-
- def __init__(self, source):
- """Initialises the HTMLInputStream.
-
- HTMLInputStream(source, [encoding]) -> Normalized stream from source
- for use by html5lib.
-
- source can be either a file-object, local filename or a string.
-
- The optional encoding parameter must be a string that indicates
- the encoding. If specified, that encoding will be used,
- regardless of any BOM or later declaration (such as in a meta
- element)
-
- """
-
- if not _utils.supports_lone_surrogates:
- # Such platforms will have already checked for such
- # surrogate errors, so no need to do this checking.
- self.reportCharacterErrors = None
- elif len("\U0010FFFF") == 1:
- self.reportCharacterErrors = self.characterErrorsUCS4
- else:
- self.reportCharacterErrors = self.characterErrorsUCS2
-
- # List of where new lines occur
- self.newLines = [0]
-
- self.charEncoding = (lookupEncoding("utf-8"), "certain")
- self.dataStream = self.openStream(source)
-
- self.reset()
-
- def reset(self):
- self.chunk = ""
- self.chunkSize = 0
- self.chunkOffset = 0
- self.errors = []
-
- # number of (complete) lines in previous chunks
- self.prevNumLines = 0
- # number of columns in the last line of the previous chunk
- self.prevNumCols = 0
-
- # Deal with CR LF and surrogates split over chunk boundaries
- self._bufferedCharacter = None
-
- def openStream(self, source):
- """Produces a file object from source.
-
- source can be either a file object, local filename or a string.
-
- """
- # Already a file object
- if hasattr(source, 'read'):
- stream = source
- else:
- stream = StringIO(source)
-
- return stream
-
- def _position(self, offset):
- chunk = self.chunk
- nLines = chunk.count('\n', 0, offset)
- positionLine = self.prevNumLines + nLines
- lastLinePos = chunk.rfind('\n', 0, offset)
- if lastLinePos == -1:
- positionColumn = self.prevNumCols + offset
- else:
- positionColumn = offset - (lastLinePos + 1)
- return (positionLine, positionColumn)
-
- def position(self):
- """Returns (line, col) of the current position in the stream."""
- line, col = self._position(self.chunkOffset)
- return (line + 1, col)
-
- def char(self):
- """ Read one character from the stream or queue if available. Return
- EOF when EOF is reached.
- """
- # Read a new chunk from the input stream if necessary
- if self.chunkOffset >= self.chunkSize:
- if not self.readChunk():
- return EOF
-
- chunkOffset = self.chunkOffset
- char = self.chunk[chunkOffset]
- self.chunkOffset = chunkOffset + 1
-
- return char
-
- def readChunk(self, chunkSize=None):
- if chunkSize is None:
- chunkSize = self._defaultChunkSize
-
- self.prevNumLines, self.prevNumCols = self._position(self.chunkSize)
-
- self.chunk = ""
- self.chunkSize = 0
- self.chunkOffset = 0
-
- data = self.dataStream.read(chunkSize)
-
- # Deal with CR LF and surrogates broken across chunks
- if self._bufferedCharacter:
- data = self._bufferedCharacter + data
- self._bufferedCharacter = None
- elif not data:
- # We have no more data, bye-bye stream
- return False
-
- if len(data) > 1:
- lastv = ord(data[-1])
- if lastv == 0x0D or 0xD800 <= lastv <= 0xDBFF:
- self._bufferedCharacter = data[-1]
- data = data[:-1]
-
- if self.reportCharacterErrors:
- self.reportCharacterErrors(data)
-
- # Replace invalid characters
- data = data.replace("\r\n", "\n")
- data = data.replace("\r", "\n")
-
- self.chunk = data
- self.chunkSize = len(data)
-
- return True
-
- def characterErrorsUCS4(self, data):
- for _ in range(len(invalid_unicode_re.findall(data))):
- self.errors.append("invalid-codepoint")
-
- def characterErrorsUCS2(self, data):
- # Someone picked the wrong compile option
- # You lose
- skip = False
- for match in invalid_unicode_re.finditer(data):
- if skip:
- continue
- codepoint = ord(match.group())
- pos = match.start()
- # Pretty sure there should be endianness issues here
- if _utils.isSurrogatePair(data[pos:pos + 2]):
- # We have a surrogate pair!
- char_val = _utils.surrogatePairToCodepoint(data[pos:pos + 2])
- if char_val in non_bmp_invalid_codepoints:
- self.errors.append("invalid-codepoint")
- skip = True
- elif (codepoint >= 0xD800 and codepoint <= 0xDFFF and
- pos == len(data) - 1):
- self.errors.append("invalid-codepoint")
- else:
- skip = False
- self.errors.append("invalid-codepoint")
-
- def charsUntil(self, characters, opposite=False):
- """ Returns a string of characters from the stream up to but not
- including any character in 'characters' or EOF. 'characters' must be
- a container that supports the 'in' method and iteration over its
- characters.
- """
-
- # Use a cache of regexps to find the required characters
- try:
- chars = charsUntilRegEx[(characters, opposite)]
- except KeyError:
- if __debug__:
- for c in characters:
- assert(ord(c) < 128)
- regex = "".join(["\\x%02x" % ord(c) for c in characters])
- if not opposite:
- regex = "^%s" % regex
- chars = charsUntilRegEx[(characters, opposite)] = re.compile("[%s]+" % regex)
-
- rv = []
-
- while True:
- # Find the longest matching prefix
- m = chars.match(self.chunk, self.chunkOffset)
- if m is None:
- # If nothing matched, and it wasn't because we ran out of chunk,
- # then stop
- if self.chunkOffset != self.chunkSize:
- break
- else:
- end = m.end()
- # If not the whole chunk matched, return everything
- # up to the part that didn't match
- if end != self.chunkSize:
- rv.append(self.chunk[self.chunkOffset:end])
- self.chunkOffset = end
- break
- # If the whole remainder of the chunk matched,
- # use it all and read the next chunk
- rv.append(self.chunk[self.chunkOffset:])
- if not self.readChunk():
- # Reached EOF
- break
-
- r = "".join(rv)
- return r
-
- def unget(self, char):
- # Only one character is allowed to be ungotten at once - it must
- # be consumed again before any further call to unget
- if char is not EOF:
- if self.chunkOffset == 0:
- # unget is called quite rarely, so it's a good idea to do
- # more work here if it saves a bit of work in the frequently
- # called char and charsUntil.
- # So, just prepend the ungotten character onto the current
- # chunk:
- self.chunk = char + self.chunk
- self.chunkSize += 1
- else:
- self.chunkOffset -= 1
- assert self.chunk[self.chunkOffset] == char
-
-
-class HTMLBinaryInputStream(HTMLUnicodeInputStream):
- """Provides a unicode stream of characters to the HTMLTokenizer.
-
- This class takes care of character encoding and removing or replacing
- incorrect byte-sequences and also provides column and line tracking.
-
- """
-
- def __init__(self, source, override_encoding=None, transport_encoding=None,
- same_origin_parent_encoding=None, likely_encoding=None,
- default_encoding="windows-1252", useChardet=True):
- """Initialises the HTMLInputStream.
-
- HTMLInputStream(source, [encoding]) -> Normalized stream from source
- for use by html5lib.
-
- source can be either a file-object, local filename or a string.
-
- The optional encoding parameter must be a string that indicates
- the encoding. If specified, that encoding will be used,
- regardless of any BOM or later declaration (such as in a meta
- element)
-
- """
- # Raw Stream - for unicode objects this will encode to utf-8 and set
- # self.charEncoding as appropriate
- self.rawStream = self.openStream(source)
-
- HTMLUnicodeInputStream.__init__(self, self.rawStream)
-
- # Encoding Information
- # Number of bytes to use when looking for a meta element with
- # encoding information
- self.numBytesMeta = 1024
- # Number of bytes to use when using detecting encoding using chardet
- self.numBytesChardet = 100
- # Things from args
- self.override_encoding = override_encoding
- self.transport_encoding = transport_encoding
- self.same_origin_parent_encoding = same_origin_parent_encoding
- self.likely_encoding = likely_encoding
- self.default_encoding = default_encoding
-
- # Determine encoding
- self.charEncoding = self.determineEncoding(useChardet)
- assert self.charEncoding[0] is not None
-
- # Call superclass
- self.reset()
-
- def reset(self):
- self.dataStream = self.charEncoding[0].codec_info.streamreader(self.rawStream, 'replace')
- HTMLUnicodeInputStream.reset(self)
-
- def openStream(self, source):
- """Produces a file object from source.
-
- source can be either a file object, local filename or a string.
-
- """
- # Already a file object
- if hasattr(source, 'read'):
- stream = source
- else:
- stream = BytesIO(source)
-
- try:
- stream.seek(stream.tell())
- except Exception:
- stream = BufferedStream(stream)
-
- return stream
-
- def determineEncoding(self, chardet=True):
- # BOMs take precedence over everything
- # This will also read past the BOM if present
- charEncoding = self.detectBOM(), "certain"
- if charEncoding[0] is not None:
- return charEncoding
-
- # If we've been overridden, we've been overridden
- charEncoding = lookupEncoding(self.override_encoding), "certain"
- if charEncoding[0] is not None:
- return charEncoding
-
- # Now check the transport layer
- charEncoding = lookupEncoding(self.transport_encoding), "certain"
- if charEncoding[0] is not None:
- return charEncoding
-
- # Look for meta elements with encoding information
- charEncoding = self.detectEncodingMeta(), "tentative"
- if charEncoding[0] is not None:
- return charEncoding
-
- # Parent document encoding
- charEncoding = lookupEncoding(self.same_origin_parent_encoding), "tentative"
- if charEncoding[0] is not None and not charEncoding[0].name.startswith("utf-16"):
- return charEncoding
-
- # "likely" encoding
- charEncoding = lookupEncoding(self.likely_encoding), "tentative"
- if charEncoding[0] is not None:
- return charEncoding
-
- # Guess with chardet, if available
- if chardet:
- try:
- from pip._vendor.chardet.universaldetector import UniversalDetector
- except ImportError:
- pass
- else:
- buffers = []
- detector = UniversalDetector()
- while not detector.done:
- buffer = self.rawStream.read(self.numBytesChardet)
- assert isinstance(buffer, bytes)
- if not buffer:
- break
- buffers.append(buffer)
- detector.feed(buffer)
- detector.close()
- encoding = lookupEncoding(detector.result['encoding'])
- self.rawStream.seek(0)
- if encoding is not None:
- return encoding, "tentative"
-
- # Try the default encoding
- charEncoding = lookupEncoding(self.default_encoding), "tentative"
- if charEncoding[0] is not None:
- return charEncoding
-
- # Fallback to html5lib's default if even that hasn't worked
- return lookupEncoding("windows-1252"), "tentative"
-
- def changeEncoding(self, newEncoding):
- assert self.charEncoding[1] != "certain"
- newEncoding = lookupEncoding(newEncoding)
- if newEncoding is None:
- return
- if newEncoding.name in ("utf-16be", "utf-16le"):
- newEncoding = lookupEncoding("utf-8")
- assert newEncoding is not None
- elif newEncoding == self.charEncoding[0]:
- self.charEncoding = (self.charEncoding[0], "certain")
- else:
- self.rawStream.seek(0)
- self.charEncoding = (newEncoding, "certain")
- self.reset()
- raise _ReparseException("Encoding changed from %s to %s" % (self.charEncoding[0], newEncoding))
-
- def detectBOM(self):
- """Attempts to detect at BOM at the start of the stream. If
- an encoding can be determined from the BOM return the name of the
- encoding otherwise return None"""
- bomDict = {
- codecs.BOM_UTF8: 'utf-8',
- codecs.BOM_UTF16_LE: 'utf-16le', codecs.BOM_UTF16_BE: 'utf-16be',
- codecs.BOM_UTF32_LE: 'utf-32le', codecs.BOM_UTF32_BE: 'utf-32be'
- }
-
- # Go to beginning of file and read in 4 bytes
- string = self.rawStream.read(4)
- assert isinstance(string, bytes)
-
- # Try detecting the BOM using bytes from the string
- encoding = bomDict.get(string[:3]) # UTF-8
- seek = 3
- if not encoding:
- # Need to detect UTF-32 before UTF-16
- encoding = bomDict.get(string) # UTF-32
- seek = 4
- if not encoding:
- encoding = bomDict.get(string[:2]) # UTF-16
- seek = 2
-
- # Set the read position past the BOM if one was found, otherwise
- # set it to the start of the stream
- if encoding:
- self.rawStream.seek(seek)
- return lookupEncoding(encoding)
- else:
- self.rawStream.seek(0)
- return None
-
- def detectEncodingMeta(self):
- """Report the encoding declared by the meta element
- """
- buffer = self.rawStream.read(self.numBytesMeta)
- assert isinstance(buffer, bytes)
- parser = EncodingParser(buffer)
- self.rawStream.seek(0)
- encoding = parser.getEncoding()
-
- if encoding is not None and encoding.name in ("utf-16be", "utf-16le"):
- encoding = lookupEncoding("utf-8")
-
- return encoding
-
-
-class EncodingBytes(bytes):
- """String-like object with an associated position and various extra methods
- If the position is ever greater than the string length then an exception is
- raised"""
- def __new__(self, value):
- assert isinstance(value, bytes)
- return bytes.__new__(self, value.lower())
-
- def __init__(self, value):
- # pylint:disable=unused-argument
- self._position = -1
-
- def __iter__(self):
- return self
-
- def __next__(self):
- p = self._position = self._position + 1
- if p >= len(self):
- raise StopIteration
- elif p < 0:
- raise TypeError
- return self[p:p + 1]
-
- def next(self):
- # Py2 compat
- return self.__next__()
-
- def previous(self):
- p = self._position
- if p >= len(self):
- raise StopIteration
- elif p < 0:
- raise TypeError
- self._position = p = p - 1
- return self[p:p + 1]
-
- def setPosition(self, position):
- if self._position >= len(self):
- raise StopIteration
- self._position = position
-
- def getPosition(self):
- if self._position >= len(self):
- raise StopIteration
- if self._position >= 0:
- return self._position
- else:
- return None
-
- position = property(getPosition, setPosition)
-
- def getCurrentByte(self):
- return self[self.position:self.position + 1]
-
- currentByte = property(getCurrentByte)
-
- def skip(self, chars=spaceCharactersBytes):
- """Skip past a list of characters"""
- p = self.position # use property for the error-checking
- while p < len(self):
- c = self[p:p + 1]
- if c not in chars:
- self._position = p
- return c
- p += 1
- self._position = p
- return None
-
- def skipUntil(self, chars):
- p = self.position
- while p < len(self):
- c = self[p:p + 1]
- if c in chars:
- self._position = p
- return c
- p += 1
- self._position = p
- return None
-
- def matchBytes(self, bytes):
- """Look for a sequence of bytes at the start of a string. If the bytes
- are found return True and advance the position to the byte after the
- match. Otherwise return False and leave the position alone"""
- rv = self.startswith(bytes, self.position)
- if rv:
- self.position += len(bytes)
- return rv
-
- def jumpTo(self, bytes):
- """Look for the next sequence of bytes matching a given sequence. If
- a match is found advance the position to the last byte of the match"""
- try:
- self._position = self.index(bytes, self.position) + len(bytes) - 1
- except ValueError:
- raise StopIteration
- return True
-
-
-class EncodingParser(object):
- """Mini parser for detecting character encoding from meta elements"""
-
- def __init__(self, data):
- """string - the data to work on for encoding detection"""
- self.data = EncodingBytes(data)
- self.encoding = None
-
- def getEncoding(self):
- if b"<meta" not in self.data:
- return None
-
- methodDispatch = (
- (b"<!--", self.handleComment),
- (b"<meta", self.handleMeta),
- (b"</", self.handlePossibleEndTag),
- (b"<!", self.handleOther),
- (b"<?", self.handleOther),
- (b"<", self.handlePossibleStartTag))
- for _ in self.data:
- keepParsing = True
- try:
- self.data.jumpTo(b"<")
- except StopIteration:
- break
- for key, method in methodDispatch:
- if self.data.matchBytes(key):
- try:
- keepParsing = method()
- break
- except StopIteration:
- keepParsing = False
- break
- if not keepParsing:
- break
-
- return self.encoding
-
- def handleComment(self):
- """Skip over comments"""
- return self.data.jumpTo(b"-->")
-
- def handleMeta(self):
- if self.data.currentByte not in spaceCharactersBytes:
- # if we have <meta not followed by a space so just keep going
- return True
- # We have a valid meta element we want to search for attributes
- hasPragma = False
- pendingEncoding = None
- while True:
- # Try to find the next attribute after the current position
- attr = self.getAttribute()
- if attr is None:
- return True
- else:
- if attr[0] == b"http-equiv":
- hasPragma = attr[1] == b"content-type"
- if hasPragma and pendingEncoding is not None:
- self.encoding = pendingEncoding
- return False
- elif attr[0] == b"charset":
- tentativeEncoding = attr[1]
- codec = lookupEncoding(tentativeEncoding)
- if codec is not None:
- self.encoding = codec
- return False
- elif attr[0] == b"content":
- contentParser = ContentAttrParser(EncodingBytes(attr[1]))
- tentativeEncoding = contentParser.parse()
- if tentativeEncoding is not None:
- codec = lookupEncoding(tentativeEncoding)
- if codec is not None:
- if hasPragma:
- self.encoding = codec
- return False
- else:
- pendingEncoding = codec
-
- def handlePossibleStartTag(self):
- return self.handlePossibleTag(False)
-
- def handlePossibleEndTag(self):
- next(self.data)
- return self.handlePossibleTag(True)
-
- def handlePossibleTag(self, endTag):
- data = self.data
- if data.currentByte not in asciiLettersBytes:
- # If the next byte is not an ascii letter either ignore this
- # fragment (possible start tag case) or treat it according to
- # handleOther
- if endTag:
- data.previous()
- self.handleOther()
- return True
-
- c = data.skipUntil(spacesAngleBrackets)
- if c == b"<":
- # return to the first step in the overall "two step" algorithm
- # reprocessing the < byte
- data.previous()
- else:
- # Read all attributes
- attr = self.getAttribute()
- while attr is not None:
- attr = self.getAttribute()
- return True
-
- def handleOther(self):
- return self.data.jumpTo(b">")
-
- def getAttribute(self):
- """Return a name,value pair for the next attribute in the stream,
- if one is found, or None"""
- data = self.data
- # Step 1 (skip chars)
- c = data.skip(spaceCharactersBytes | frozenset([b"/"]))
- assert c is None or len(c) == 1
- # Step 2
- if c in (b">", None):
- return None
- # Step 3
- attrName = []
- attrValue = []
- # Step 4 attribute name
- while True:
- if c == b"=" and attrName:
- break
- elif c in spaceCharactersBytes:
- # Step 6!
- c = data.skip()
- break
- elif c in (b"/", b">"):
- return b"".join(attrName), b""
- elif c in asciiUppercaseBytes:
- attrName.append(c.lower())
- elif c is None:
- return None
- else:
- attrName.append(c)
- # Step 5
- c = next(data)
- # Step 7
- if c != b"=":
- data.previous()
- return b"".join(attrName), b""
- # Step 8
- next(data)
- # Step 9
- c = data.skip()
- # Step 10
- if c in (b"'", b'"'):
- # 10.1
- quoteChar = c
- while True:
- # 10.2
- c = next(data)
- # 10.3
- if c == quoteChar:
- next(data)
- return b"".join(attrName), b"".join(attrValue)
- # 10.4
- elif c in asciiUppercaseBytes:
- attrValue.append(c.lower())
- # 10.5
- else:
- attrValue.append(c)
- elif c == b">":
- return b"".join(attrName), b""
- elif c in asciiUppercaseBytes:
- attrValue.append(c.lower())
- elif c is None:
- return None
- else:
- attrValue.append(c)
- # Step 11
- while True:
- c = next(data)
- if c in spacesAngleBrackets:
- return b"".join(attrName), b"".join(attrValue)
- elif c in asciiUppercaseBytes:
- attrValue.append(c.lower())
- elif c is None:
- return None
- else:
- attrValue.append(c)
-
-
-class ContentAttrParser(object):
- def __init__(self, data):
- assert isinstance(data, bytes)
- self.data = data
-
- def parse(self):
- try:
- # Check if the attr name is charset
- # otherwise return
- self.data.jumpTo(b"charset")
- self.data.position += 1
- self.data.skip()
- if not self.data.currentByte == b"=":
- # If there is no = sign keep looking for attrs
- return None
- self.data.position += 1
- self.data.skip()
- # Look for an encoding between matching quote marks
- if self.data.currentByte in (b'"', b"'"):
- quoteMark = self.data.currentByte
- self.data.position += 1
- oldPosition = self.data.position
- if self.data.jumpTo(quoteMark):
- return self.data[oldPosition:self.data.position]
- else:
- return None
- else:
- # Unquoted value
- oldPosition = self.data.position
- try:
- self.data.skipUntil(spaceCharactersBytes)
- return self.data[oldPosition:self.data.position]
- except StopIteration:
- # Return the whole remaining value
- return self.data[oldPosition:]
- except StopIteration:
- return None
-
-
-def lookupEncoding(encoding):
- """Return the python codec name corresponding to an encoding or None if the
- string doesn't correspond to a valid encoding."""
- if isinstance(encoding, bytes):
- try:
- encoding = encoding.decode("ascii")
- except UnicodeDecodeError:
- return None
-
- if encoding is not None:
- try:
- return webencodings.lookup(encoding)
- except AttributeError:
- return None
- else:
- return None
diff --git a/src/pip/_vendor/html5lib/_tokenizer.py b/src/pip/_vendor/html5lib/_tokenizer.py
deleted file mode 100644
index 5f00253e2..000000000
--- a/src/pip/_vendor/html5lib/_tokenizer.py
+++ /dev/null
@@ -1,1735 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-from pip._vendor.six import unichr as chr
-
-from collections import deque, OrderedDict
-from sys import version_info
-
-from .constants import spaceCharacters
-from .constants import entities
-from .constants import asciiLetters, asciiUpper2Lower
-from .constants import digits, hexDigits, EOF
-from .constants import tokenTypes, tagTokenTypes
-from .constants import replacementCharacters
-
-from ._inputstream import HTMLInputStream
-
-from ._trie import Trie
-
-entitiesTrie = Trie(entities)
-
-if version_info >= (3, 7):
- attributeMap = dict
-else:
- attributeMap = OrderedDict
-
-
-class HTMLTokenizer(object):
- """ This class takes care of tokenizing HTML.
-
- * self.currentToken
- Holds the token that is currently being processed.
-
- * self.state
- Holds a reference to the method to be invoked... XXX
-
- * self.stream
- Points to HTMLInputStream object.
- """
-
- def __init__(self, stream, parser=None, **kwargs):
-
- self.stream = HTMLInputStream(stream, **kwargs)
- self.parser = parser
-
- # Setup the initial tokenizer state
- self.escapeFlag = False
- self.lastFourChars = []
- self.state = self.dataState
- self.escape = False
-
- # The current token being created
- self.currentToken = None
- super(HTMLTokenizer, self).__init__()
-
- def __iter__(self):
- """ This is where the magic happens.
-
- We do our usually processing through the states and when we have a token
- to return we yield the token which pauses processing until the next token
- is requested.
- """
- self.tokenQueue = deque([])
- # Start processing. When EOF is reached self.state will return False
- # instead of True and the loop will terminate.
- while self.state():
- while self.stream.errors:
- yield {"type": tokenTypes["ParseError"], "data": self.stream.errors.pop(0)}
- while self.tokenQueue:
- yield self.tokenQueue.popleft()
-
- def consumeNumberEntity(self, isHex):
- """This function returns either U+FFFD or the character based on the
- decimal or hexadecimal representation. It also discards ";" if present.
- If not present self.tokenQueue.append({"type": tokenTypes["ParseError"]}) is invoked.
- """
-
- allowed = digits
- radix = 10
- if isHex:
- allowed = hexDigits
- radix = 16
-
- charStack = []
-
- # Consume all the characters that are in range while making sure we
- # don't hit an EOF.
- c = self.stream.char()
- while c in allowed and c is not EOF:
- charStack.append(c)
- c = self.stream.char()
-
- # Convert the set of characters consumed to an int.
- charAsInt = int("".join(charStack), radix)
-
- # Certain characters get replaced with others
- if charAsInt in replacementCharacters:
- char = replacementCharacters[charAsInt]
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "illegal-codepoint-for-numeric-entity",
- "datavars": {"charAsInt": charAsInt}})
- elif ((0xD800 <= charAsInt <= 0xDFFF) or
- (charAsInt > 0x10FFFF)):
- char = "\uFFFD"
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "illegal-codepoint-for-numeric-entity",
- "datavars": {"charAsInt": charAsInt}})
- else:
- # Should speed up this check somehow (e.g. move the set to a constant)
- if ((0x0001 <= charAsInt <= 0x0008) or
- (0x000E <= charAsInt <= 0x001F) or
- (0x007F <= charAsInt <= 0x009F) or
- (0xFDD0 <= charAsInt <= 0xFDEF) or
- charAsInt in frozenset([0x000B, 0xFFFE, 0xFFFF, 0x1FFFE,
- 0x1FFFF, 0x2FFFE, 0x2FFFF, 0x3FFFE,
- 0x3FFFF, 0x4FFFE, 0x4FFFF, 0x5FFFE,
- 0x5FFFF, 0x6FFFE, 0x6FFFF, 0x7FFFE,
- 0x7FFFF, 0x8FFFE, 0x8FFFF, 0x9FFFE,
- 0x9FFFF, 0xAFFFE, 0xAFFFF, 0xBFFFE,
- 0xBFFFF, 0xCFFFE, 0xCFFFF, 0xDFFFE,
- 0xDFFFF, 0xEFFFE, 0xEFFFF, 0xFFFFE,
- 0xFFFFF, 0x10FFFE, 0x10FFFF])):
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data":
- "illegal-codepoint-for-numeric-entity",
- "datavars": {"charAsInt": charAsInt}})
- try:
- # Try/except needed as UCS-2 Python builds' unichar only works
- # within the BMP.
- char = chr(charAsInt)
- except ValueError:
- v = charAsInt - 0x10000
- char = chr(0xD800 | (v >> 10)) + chr(0xDC00 | (v & 0x3FF))
-
- # Discard the ; if present. Otherwise, put it back on the queue and
- # invoke parseError on parser.
- if c != ";":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "numeric-entity-without-semicolon"})
- self.stream.unget(c)
-
- return char
-
- def consumeEntity(self, allowedChar=None, fromAttribute=False):
- # Initialise to the default output for when no entity is matched
- output = "&"
-
- charStack = [self.stream.char()]
- if (charStack[0] in spaceCharacters or charStack[0] in (EOF, "<", "&") or
- (allowedChar is not None and allowedChar == charStack[0])):
- self.stream.unget(charStack[0])
-
- elif charStack[0] == "#":
- # Read the next character to see if it's hex or decimal
- hex = False
- charStack.append(self.stream.char())
- if charStack[-1] in ("x", "X"):
- hex = True
- charStack.append(self.stream.char())
-
- # charStack[-1] should be the first digit
- if (hex and charStack[-1] in hexDigits) \
- or (not hex and charStack[-1] in digits):
- # At least one digit found, so consume the whole number
- self.stream.unget(charStack[-1])
- output = self.consumeNumberEntity(hex)
- else:
- # No digits found
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "expected-numeric-entity"})
- self.stream.unget(charStack.pop())
- output = "&" + "".join(charStack)
-
- else:
- # At this point in the process might have named entity. Entities
- # are stored in the global variable "entities".
- #
- # Consume characters and compare to these to a substring of the
- # entity names in the list until the substring no longer matches.
- while (charStack[-1] is not EOF):
- if not entitiesTrie.has_keys_with_prefix("".join(charStack)):
- break
- charStack.append(self.stream.char())
-
- # At this point we have a string that starts with some characters
- # that may match an entity
- # Try to find the longest entity the string will match to take care
- # of &noti for instance.
- try:
- entityName = entitiesTrie.longest_prefix("".join(charStack[:-1]))
- entityLength = len(entityName)
- except KeyError:
- entityName = None
-
- if entityName is not None:
- if entityName[-1] != ";":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "named-entity-without-semicolon"})
- if (entityName[-1] != ";" and fromAttribute and
- (charStack[entityLength] in asciiLetters or
- charStack[entityLength] in digits or
- charStack[entityLength] == "=")):
- self.stream.unget(charStack.pop())
- output = "&" + "".join(charStack)
- else:
- output = entities[entityName]
- self.stream.unget(charStack.pop())
- output += "".join(charStack[entityLength:])
- else:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-named-entity"})
- self.stream.unget(charStack.pop())
- output = "&" + "".join(charStack)
-
- if fromAttribute:
- self.currentToken["data"][-1][1] += output
- else:
- if output in spaceCharacters:
- tokenType = "SpaceCharacters"
- else:
- tokenType = "Characters"
- self.tokenQueue.append({"type": tokenTypes[tokenType], "data": output})
-
- def processEntityInAttribute(self, allowedChar):
- """This method replaces the need for "entityInAttributeValueState".
- """
- self.consumeEntity(allowedChar=allowedChar, fromAttribute=True)
-
- def emitCurrentToken(self):
- """This method is a generic handler for emitting the tags. It also sets
- the state to "data" because that's what's needed after a token has been
- emitted.
- """
- token = self.currentToken
- # Add token to the queue to be yielded
- if (token["type"] in tagTokenTypes):
- token["name"] = token["name"].translate(asciiUpper2Lower)
- if token["type"] == tokenTypes["StartTag"]:
- raw = token["data"]
- data = attributeMap(raw)
- if len(raw) > len(data):
- # we had some duplicated attribute, fix so first wins
- data.update(raw[::-1])
- token["data"] = data
-
- if token["type"] == tokenTypes["EndTag"]:
- if token["data"]:
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "attributes-in-end-tag"})
- if token["selfClosing"]:
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "self-closing-flag-on-end-tag"})
- self.tokenQueue.append(token)
- self.state = self.dataState
-
- # Below are the various tokenizer states worked out.
- def dataState(self):
- data = self.stream.char()
- if data == "&":
- self.state = self.entityDataState
- elif data == "<":
- self.state = self.tagOpenState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": "\u0000"})
- elif data is EOF:
- # Tokenization ends.
- return False
- elif data in spaceCharacters:
- # Directly after emitting a token you switch back to the "data
- # state". At that point spaceCharacters are important so they are
- # emitted separately.
- self.tokenQueue.append({"type": tokenTypes["SpaceCharacters"], "data":
- data + self.stream.charsUntil(spaceCharacters, True)})
- # No need to update lastFourChars here, since the first space will
- # have already been appended to lastFourChars and will have broken
- # any <!-- or --> sequences
- else:
- chars = self.stream.charsUntil(("&", "<", "\u0000"))
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data":
- data + chars})
- return True
-
- def entityDataState(self):
- self.consumeEntity()
- self.state = self.dataState
- return True
-
- def rcdataState(self):
- data = self.stream.char()
- if data == "&":
- self.state = self.characterReferenceInRcdata
- elif data == "<":
- self.state = self.rcdataLessThanSignState
- elif data == EOF:
- # Tokenization ends.
- return False
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": "\uFFFD"})
- elif data in spaceCharacters:
- # Directly after emitting a token you switch back to the "data
- # state". At that point spaceCharacters are important so they are
- # emitted separately.
- self.tokenQueue.append({"type": tokenTypes["SpaceCharacters"], "data":
- data + self.stream.charsUntil(spaceCharacters, True)})
- # No need to update lastFourChars here, since the first space will
- # have already been appended to lastFourChars and will have broken
- # any <!-- or --> sequences
- else:
- chars = self.stream.charsUntil(("&", "<", "\u0000"))
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data":
- data + chars})
- return True
-
- def characterReferenceInRcdata(self):
- self.consumeEntity()
- self.state = self.rcdataState
- return True
-
- def rawtextState(self):
- data = self.stream.char()
- if data == "<":
- self.state = self.rawtextLessThanSignState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": "\uFFFD"})
- elif data == EOF:
- # Tokenization ends.
- return False
- else:
- chars = self.stream.charsUntil(("<", "\u0000"))
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data":
- data + chars})
- return True
-
- def scriptDataState(self):
- data = self.stream.char()
- if data == "<":
- self.state = self.scriptDataLessThanSignState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": "\uFFFD"})
- elif data == EOF:
- # Tokenization ends.
- return False
- else:
- chars = self.stream.charsUntil(("<", "\u0000"))
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data":
- data + chars})
- return True
-
- def plaintextState(self):
- data = self.stream.char()
- if data == EOF:
- # Tokenization ends.
- return False
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": "\uFFFD"})
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data":
- data + self.stream.charsUntil("\u0000")})
- return True
-
- def tagOpenState(self):
- data = self.stream.char()
- if data == "!":
- self.state = self.markupDeclarationOpenState
- elif data == "/":
- self.state = self.closeTagOpenState
- elif data in asciiLetters:
- self.currentToken = {"type": tokenTypes["StartTag"],
- "name": data, "data": [],
- "selfClosing": False,
- "selfClosingAcknowledged": False}
- self.state = self.tagNameState
- elif data == ">":
- # XXX In theory it could be something besides a tag name. But
- # do we really care?
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-tag-name-but-got-right-bracket"})
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<>"})
- self.state = self.dataState
- elif data == "?":
- # XXX In theory it could be something besides a tag name. But
- # do we really care?
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-tag-name-but-got-question-mark"})
- self.stream.unget(data)
- self.state = self.bogusCommentState
- else:
- # XXX
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-tag-name"})
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<"})
- self.stream.unget(data)
- self.state = self.dataState
- return True
-
- def closeTagOpenState(self):
- data = self.stream.char()
- if data in asciiLetters:
- self.currentToken = {"type": tokenTypes["EndTag"], "name": data,
- "data": [], "selfClosing": False}
- self.state = self.tagNameState
- elif data == ">":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-closing-tag-but-got-right-bracket"})
- self.state = self.dataState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-closing-tag-but-got-eof"})
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "</"})
- self.state = self.dataState
- else:
- # XXX data can be _'_...
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-closing-tag-but-got-char",
- "datavars": {"data": data}})
- self.stream.unget(data)
- self.state = self.bogusCommentState
- return True
-
- def tagNameState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- self.state = self.beforeAttributeNameState
- elif data == ">":
- self.emitCurrentToken()
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-tag-name"})
- self.state = self.dataState
- elif data == "/":
- self.state = self.selfClosingStartTagState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["name"] += "\uFFFD"
- else:
- self.currentToken["name"] += data
- # (Don't use charsUntil here, because tag names are
- # very short and it's faster to not do anything fancy)
- return True
-
- def rcdataLessThanSignState(self):
- data = self.stream.char()
- if data == "/":
- self.temporaryBuffer = ""
- self.state = self.rcdataEndTagOpenState
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<"})
- self.stream.unget(data)
- self.state = self.rcdataState
- return True
-
- def rcdataEndTagOpenState(self):
- data = self.stream.char()
- if data in asciiLetters:
- self.temporaryBuffer += data
- self.state = self.rcdataEndTagNameState
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "</"})
- self.stream.unget(data)
- self.state = self.rcdataState
- return True
-
- def rcdataEndTagNameState(self):
- appropriate = self.currentToken and self.currentToken["name"].lower() == self.temporaryBuffer.lower()
- data = self.stream.char()
- if data in spaceCharacters and appropriate:
- self.currentToken = {"type": tokenTypes["EndTag"],
- "name": self.temporaryBuffer,
- "data": [], "selfClosing": False}
- self.state = self.beforeAttributeNameState
- elif data == "/" and appropriate:
- self.currentToken = {"type": tokenTypes["EndTag"],
- "name": self.temporaryBuffer,
- "data": [], "selfClosing": False}
- self.state = self.selfClosingStartTagState
- elif data == ">" and appropriate:
- self.currentToken = {"type": tokenTypes["EndTag"],
- "name": self.temporaryBuffer,
- "data": [], "selfClosing": False}
- self.emitCurrentToken()
- self.state = self.dataState
- elif data in asciiLetters:
- self.temporaryBuffer += data
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": "</" + self.temporaryBuffer})
- self.stream.unget(data)
- self.state = self.rcdataState
- return True
-
- def rawtextLessThanSignState(self):
- data = self.stream.char()
- if data == "/":
- self.temporaryBuffer = ""
- self.state = self.rawtextEndTagOpenState
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<"})
- self.stream.unget(data)
- self.state = self.rawtextState
- return True
-
- def rawtextEndTagOpenState(self):
- data = self.stream.char()
- if data in asciiLetters:
- self.temporaryBuffer += data
- self.state = self.rawtextEndTagNameState
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "</"})
- self.stream.unget(data)
- self.state = self.rawtextState
- return True
-
- def rawtextEndTagNameState(self):
- appropriate = self.currentToken and self.currentToken["name"].lower() == self.temporaryBuffer.lower()
- data = self.stream.char()
- if data in spaceCharacters and appropriate:
- self.currentToken = {"type": tokenTypes["EndTag"],
- "name": self.temporaryBuffer,
- "data": [], "selfClosing": False}
- self.state = self.beforeAttributeNameState
- elif data == "/" and appropriate:
- self.currentToken = {"type": tokenTypes["EndTag"],
- "name": self.temporaryBuffer,
- "data": [], "selfClosing": False}
- self.state = self.selfClosingStartTagState
- elif data == ">" and appropriate:
- self.currentToken = {"type": tokenTypes["EndTag"],
- "name": self.temporaryBuffer,
- "data": [], "selfClosing": False}
- self.emitCurrentToken()
- self.state = self.dataState
- elif data in asciiLetters:
- self.temporaryBuffer += data
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": "</" + self.temporaryBuffer})
- self.stream.unget(data)
- self.state = self.rawtextState
- return True
-
- def scriptDataLessThanSignState(self):
- data = self.stream.char()
- if data == "/":
- self.temporaryBuffer = ""
- self.state = self.scriptDataEndTagOpenState
- elif data == "!":
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<!"})
- self.state = self.scriptDataEscapeStartState
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<"})
- self.stream.unget(data)
- self.state = self.scriptDataState
- return True
-
- def scriptDataEndTagOpenState(self):
- data = self.stream.char()
- if data in asciiLetters:
- self.temporaryBuffer += data
- self.state = self.scriptDataEndTagNameState
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "</"})
- self.stream.unget(data)
- self.state = self.scriptDataState
- return True
-
- def scriptDataEndTagNameState(self):
- appropriate = self.currentToken and self.currentToken["name"].lower() == self.temporaryBuffer.lower()
- data = self.stream.char()
- if data in spaceCharacters and appropriate:
- self.currentToken = {"type": tokenTypes["EndTag"],
- "name": self.temporaryBuffer,
- "data": [], "selfClosing": False}
- self.state = self.beforeAttributeNameState
- elif data == "/" and appropriate:
- self.currentToken = {"type": tokenTypes["EndTag"],
- "name": self.temporaryBuffer,
- "data": [], "selfClosing": False}
- self.state = self.selfClosingStartTagState
- elif data == ">" and appropriate:
- self.currentToken = {"type": tokenTypes["EndTag"],
- "name": self.temporaryBuffer,
- "data": [], "selfClosing": False}
- self.emitCurrentToken()
- self.state = self.dataState
- elif data in asciiLetters:
- self.temporaryBuffer += data
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": "</" + self.temporaryBuffer})
- self.stream.unget(data)
- self.state = self.scriptDataState
- return True
-
- def scriptDataEscapeStartState(self):
- data = self.stream.char()
- if data == "-":
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "-"})
- self.state = self.scriptDataEscapeStartDashState
- else:
- self.stream.unget(data)
- self.state = self.scriptDataState
- return True
-
- def scriptDataEscapeStartDashState(self):
- data = self.stream.char()
- if data == "-":
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "-"})
- self.state = self.scriptDataEscapedDashDashState
- else:
- self.stream.unget(data)
- self.state = self.scriptDataState
- return True
-
- def scriptDataEscapedState(self):
- data = self.stream.char()
- if data == "-":
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "-"})
- self.state = self.scriptDataEscapedDashState
- elif data == "<":
- self.state = self.scriptDataEscapedLessThanSignState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": "\uFFFD"})
- elif data == EOF:
- self.state = self.dataState
- else:
- chars = self.stream.charsUntil(("<", "-", "\u0000"))
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data":
- data + chars})
- return True
-
- def scriptDataEscapedDashState(self):
- data = self.stream.char()
- if data == "-":
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "-"})
- self.state = self.scriptDataEscapedDashDashState
- elif data == "<":
- self.state = self.scriptDataEscapedLessThanSignState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": "\uFFFD"})
- self.state = self.scriptDataEscapedState
- elif data == EOF:
- self.state = self.dataState
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": data})
- self.state = self.scriptDataEscapedState
- return True
-
- def scriptDataEscapedDashDashState(self):
- data = self.stream.char()
- if data == "-":
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "-"})
- elif data == "<":
- self.state = self.scriptDataEscapedLessThanSignState
- elif data == ">":
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": ">"})
- self.state = self.scriptDataState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": "\uFFFD"})
- self.state = self.scriptDataEscapedState
- elif data == EOF:
- self.state = self.dataState
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": data})
- self.state = self.scriptDataEscapedState
- return True
-
- def scriptDataEscapedLessThanSignState(self):
- data = self.stream.char()
- if data == "/":
- self.temporaryBuffer = ""
- self.state = self.scriptDataEscapedEndTagOpenState
- elif data in asciiLetters:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<" + data})
- self.temporaryBuffer = data
- self.state = self.scriptDataDoubleEscapeStartState
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<"})
- self.stream.unget(data)
- self.state = self.scriptDataEscapedState
- return True
-
- def scriptDataEscapedEndTagOpenState(self):
- data = self.stream.char()
- if data in asciiLetters:
- self.temporaryBuffer = data
- self.state = self.scriptDataEscapedEndTagNameState
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "</"})
- self.stream.unget(data)
- self.state = self.scriptDataEscapedState
- return True
-
- def scriptDataEscapedEndTagNameState(self):
- appropriate = self.currentToken and self.currentToken["name"].lower() == self.temporaryBuffer.lower()
- data = self.stream.char()
- if data in spaceCharacters and appropriate:
- self.currentToken = {"type": tokenTypes["EndTag"],
- "name": self.temporaryBuffer,
- "data": [], "selfClosing": False}
- self.state = self.beforeAttributeNameState
- elif data == "/" and appropriate:
- self.currentToken = {"type": tokenTypes["EndTag"],
- "name": self.temporaryBuffer,
- "data": [], "selfClosing": False}
- self.state = self.selfClosingStartTagState
- elif data == ">" and appropriate:
- self.currentToken = {"type": tokenTypes["EndTag"],
- "name": self.temporaryBuffer,
- "data": [], "selfClosing": False}
- self.emitCurrentToken()
- self.state = self.dataState
- elif data in asciiLetters:
- self.temporaryBuffer += data
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": "</" + self.temporaryBuffer})
- self.stream.unget(data)
- self.state = self.scriptDataEscapedState
- return True
-
- def scriptDataDoubleEscapeStartState(self):
- data = self.stream.char()
- if data in (spaceCharacters | frozenset(("/", ">"))):
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": data})
- if self.temporaryBuffer.lower() == "script":
- self.state = self.scriptDataDoubleEscapedState
- else:
- self.state = self.scriptDataEscapedState
- elif data in asciiLetters:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": data})
- self.temporaryBuffer += data
- else:
- self.stream.unget(data)
- self.state = self.scriptDataEscapedState
- return True
-
- def scriptDataDoubleEscapedState(self):
- data = self.stream.char()
- if data == "-":
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "-"})
- self.state = self.scriptDataDoubleEscapedDashState
- elif data == "<":
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<"})
- self.state = self.scriptDataDoubleEscapedLessThanSignState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": "\uFFFD"})
- elif data == EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-script-in-script"})
- self.state = self.dataState
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": data})
- return True
-
- def scriptDataDoubleEscapedDashState(self):
- data = self.stream.char()
- if data == "-":
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "-"})
- self.state = self.scriptDataDoubleEscapedDashDashState
- elif data == "<":
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<"})
- self.state = self.scriptDataDoubleEscapedLessThanSignState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": "\uFFFD"})
- self.state = self.scriptDataDoubleEscapedState
- elif data == EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-script-in-script"})
- self.state = self.dataState
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": data})
- self.state = self.scriptDataDoubleEscapedState
- return True
-
- def scriptDataDoubleEscapedDashDashState(self):
- data = self.stream.char()
- if data == "-":
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "-"})
- elif data == "<":
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "<"})
- self.state = self.scriptDataDoubleEscapedLessThanSignState
- elif data == ">":
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": ">"})
- self.state = self.scriptDataState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": "\uFFFD"})
- self.state = self.scriptDataDoubleEscapedState
- elif data == EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-script-in-script"})
- self.state = self.dataState
- else:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": data})
- self.state = self.scriptDataDoubleEscapedState
- return True
-
- def scriptDataDoubleEscapedLessThanSignState(self):
- data = self.stream.char()
- if data == "/":
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": "/"})
- self.temporaryBuffer = ""
- self.state = self.scriptDataDoubleEscapeEndState
- else:
- self.stream.unget(data)
- self.state = self.scriptDataDoubleEscapedState
- return True
-
- def scriptDataDoubleEscapeEndState(self):
- data = self.stream.char()
- if data in (spaceCharacters | frozenset(("/", ">"))):
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": data})
- if self.temporaryBuffer.lower() == "script":
- self.state = self.scriptDataEscapedState
- else:
- self.state = self.scriptDataDoubleEscapedState
- elif data in asciiLetters:
- self.tokenQueue.append({"type": tokenTypes["Characters"], "data": data})
- self.temporaryBuffer += data
- else:
- self.stream.unget(data)
- self.state = self.scriptDataDoubleEscapedState
- return True
-
- def beforeAttributeNameState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- self.stream.charsUntil(spaceCharacters, True)
- elif data in asciiLetters:
- self.currentToken["data"].append([data, ""])
- self.state = self.attributeNameState
- elif data == ">":
- self.emitCurrentToken()
- elif data == "/":
- self.state = self.selfClosingStartTagState
- elif data in ("'", '"', "=", "<"):
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "invalid-character-in-attribute-name"})
- self.currentToken["data"].append([data, ""])
- self.state = self.attributeNameState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["data"].append(["\uFFFD", ""])
- self.state = self.attributeNameState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-attribute-name-but-got-eof"})
- self.state = self.dataState
- else:
- self.currentToken["data"].append([data, ""])
- self.state = self.attributeNameState
- return True
-
- def attributeNameState(self):
- data = self.stream.char()
- leavingThisState = True
- emitToken = False
- if data == "=":
- self.state = self.beforeAttributeValueState
- elif data in asciiLetters:
- self.currentToken["data"][-1][0] += data +\
- self.stream.charsUntil(asciiLetters, True)
- leavingThisState = False
- elif data == ">":
- # XXX If we emit here the attributes are converted to a dict
- # without being checked and when the code below runs we error
- # because data is a dict not a list
- emitToken = True
- elif data in spaceCharacters:
- self.state = self.afterAttributeNameState
- elif data == "/":
- self.state = self.selfClosingStartTagState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["data"][-1][0] += "\uFFFD"
- leavingThisState = False
- elif data in ("'", '"', "<"):
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data":
- "invalid-character-in-attribute-name"})
- self.currentToken["data"][-1][0] += data
- leavingThisState = False
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "eof-in-attribute-name"})
- self.state = self.dataState
- else:
- self.currentToken["data"][-1][0] += data
- leavingThisState = False
-
- if leavingThisState:
- # Attributes are not dropped at this stage. That happens when the
- # start tag token is emitted so values can still be safely appended
- # to attributes, but we do want to report the parse error in time.
- self.currentToken["data"][-1][0] = (
- self.currentToken["data"][-1][0].translate(asciiUpper2Lower))
- for name, _ in self.currentToken["data"][:-1]:
- if self.currentToken["data"][-1][0] == name:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "duplicate-attribute"})
- break
- # XXX Fix for above XXX
- if emitToken:
- self.emitCurrentToken()
- return True
-
- def afterAttributeNameState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- self.stream.charsUntil(spaceCharacters, True)
- elif data == "=":
- self.state = self.beforeAttributeValueState
- elif data == ">":
- self.emitCurrentToken()
- elif data in asciiLetters:
- self.currentToken["data"].append([data, ""])
- self.state = self.attributeNameState
- elif data == "/":
- self.state = self.selfClosingStartTagState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["data"].append(["\uFFFD", ""])
- self.state = self.attributeNameState
- elif data in ("'", '"', "<"):
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "invalid-character-after-attribute-name"})
- self.currentToken["data"].append([data, ""])
- self.state = self.attributeNameState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-end-of-tag-but-got-eof"})
- self.state = self.dataState
- else:
- self.currentToken["data"].append([data, ""])
- self.state = self.attributeNameState
- return True
-
- def beforeAttributeValueState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- self.stream.charsUntil(spaceCharacters, True)
- elif data == "\"":
- self.state = self.attributeValueDoubleQuotedState
- elif data == "&":
- self.state = self.attributeValueUnQuotedState
- self.stream.unget(data)
- elif data == "'":
- self.state = self.attributeValueSingleQuotedState
- elif data == ">":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-attribute-value-but-got-right-bracket"})
- self.emitCurrentToken()
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["data"][-1][1] += "\uFFFD"
- self.state = self.attributeValueUnQuotedState
- elif data in ("=", "<", "`"):
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "equals-in-unquoted-attribute-value"})
- self.currentToken["data"][-1][1] += data
- self.state = self.attributeValueUnQuotedState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-attribute-value-but-got-eof"})
- self.state = self.dataState
- else:
- self.currentToken["data"][-1][1] += data
- self.state = self.attributeValueUnQuotedState
- return True
-
- def attributeValueDoubleQuotedState(self):
- data = self.stream.char()
- if data == "\"":
- self.state = self.afterAttributeValueState
- elif data == "&":
- self.processEntityInAttribute('"')
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["data"][-1][1] += "\uFFFD"
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-attribute-value-double-quote"})
- self.state = self.dataState
- else:
- self.currentToken["data"][-1][1] += data +\
- self.stream.charsUntil(("\"", "&", "\u0000"))
- return True
-
- def attributeValueSingleQuotedState(self):
- data = self.stream.char()
- if data == "'":
- self.state = self.afterAttributeValueState
- elif data == "&":
- self.processEntityInAttribute("'")
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["data"][-1][1] += "\uFFFD"
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-attribute-value-single-quote"})
- self.state = self.dataState
- else:
- self.currentToken["data"][-1][1] += data +\
- self.stream.charsUntil(("'", "&", "\u0000"))
- return True
-
- def attributeValueUnQuotedState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- self.state = self.beforeAttributeNameState
- elif data == "&":
- self.processEntityInAttribute(">")
- elif data == ">":
- self.emitCurrentToken()
- elif data in ('"', "'", "=", "<", "`"):
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-character-in-unquoted-attribute-value"})
- self.currentToken["data"][-1][1] += data
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["data"][-1][1] += "\uFFFD"
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-attribute-value-no-quotes"})
- self.state = self.dataState
- else:
- self.currentToken["data"][-1][1] += data + self.stream.charsUntil(
- frozenset(("&", ">", '"', "'", "=", "<", "`", "\u0000")) | spaceCharacters)
- return True
-
- def afterAttributeValueState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- self.state = self.beforeAttributeNameState
- elif data == ">":
- self.emitCurrentToken()
- elif data == "/":
- self.state = self.selfClosingStartTagState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-EOF-after-attribute-value"})
- self.stream.unget(data)
- self.state = self.dataState
- else:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-character-after-attribute-value"})
- self.stream.unget(data)
- self.state = self.beforeAttributeNameState
- return True
-
- def selfClosingStartTagState(self):
- data = self.stream.char()
- if data == ">":
- self.currentToken["selfClosing"] = True
- self.emitCurrentToken()
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data":
- "unexpected-EOF-after-solidus-in-tag"})
- self.stream.unget(data)
- self.state = self.dataState
- else:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-character-after-solidus-in-tag"})
- self.stream.unget(data)
- self.state = self.beforeAttributeNameState
- return True
-
- def bogusCommentState(self):
- # Make a new comment token and give it as value all the characters
- # until the first > or EOF (charsUntil checks for EOF automatically)
- # and emit it.
- data = self.stream.charsUntil(">")
- data = data.replace("\u0000", "\uFFFD")
- self.tokenQueue.append(
- {"type": tokenTypes["Comment"], "data": data})
-
- # Eat the character directly after the bogus comment which is either a
- # ">" or an EOF.
- self.stream.char()
- self.state = self.dataState
- return True
-
- def markupDeclarationOpenState(self):
- charStack = [self.stream.char()]
- if charStack[-1] == "-":
- charStack.append(self.stream.char())
- if charStack[-1] == "-":
- self.currentToken = {"type": tokenTypes["Comment"], "data": ""}
- self.state = self.commentStartState
- return True
- elif charStack[-1] in ('d', 'D'):
- matched = True
- for expected in (('o', 'O'), ('c', 'C'), ('t', 'T'),
- ('y', 'Y'), ('p', 'P'), ('e', 'E')):
- charStack.append(self.stream.char())
- if charStack[-1] not in expected:
- matched = False
- break
- if matched:
- self.currentToken = {"type": tokenTypes["Doctype"],
- "name": "",
- "publicId": None, "systemId": None,
- "correct": True}
- self.state = self.doctypeState
- return True
- elif (charStack[-1] == "[" and
- self.parser is not None and
- self.parser.tree.openElements and
- self.parser.tree.openElements[-1].namespace != self.parser.tree.defaultNamespace):
- matched = True
- for expected in ["C", "D", "A", "T", "A", "["]:
- charStack.append(self.stream.char())
- if charStack[-1] != expected:
- matched = False
- break
- if matched:
- self.state = self.cdataSectionState
- return True
-
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-dashes-or-doctype"})
-
- while charStack:
- self.stream.unget(charStack.pop())
- self.state = self.bogusCommentState
- return True
-
- def commentStartState(self):
- data = self.stream.char()
- if data == "-":
- self.state = self.commentStartDashState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["data"] += "\uFFFD"
- elif data == ">":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "incorrect-comment"})
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-comment"})
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.currentToken["data"] += data
- self.state = self.commentState
- return True
-
- def commentStartDashState(self):
- data = self.stream.char()
- if data == "-":
- self.state = self.commentEndState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["data"] += "-\uFFFD"
- elif data == ">":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "incorrect-comment"})
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-comment"})
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.currentToken["data"] += "-" + data
- self.state = self.commentState
- return True
-
- def commentState(self):
- data = self.stream.char()
- if data == "-":
- self.state = self.commentEndDashState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["data"] += "\uFFFD"
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "eof-in-comment"})
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.currentToken["data"] += data + \
- self.stream.charsUntil(("-", "\u0000"))
- return True
-
- def commentEndDashState(self):
- data = self.stream.char()
- if data == "-":
- self.state = self.commentEndState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["data"] += "-\uFFFD"
- self.state = self.commentState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-comment-end-dash"})
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.currentToken["data"] += "-" + data
- self.state = self.commentState
- return True
-
- def commentEndState(self):
- data = self.stream.char()
- if data == ">":
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["data"] += "--\uFFFD"
- self.state = self.commentState
- elif data == "!":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-bang-after-double-dash-in-comment"})
- self.state = self.commentEndBangState
- elif data == "-":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-dash-after-double-dash-in-comment"})
- self.currentToken["data"] += data
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-comment-double-dash"})
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- # XXX
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-char-in-comment"})
- self.currentToken["data"] += "--" + data
- self.state = self.commentState
- return True
-
- def commentEndBangState(self):
- data = self.stream.char()
- if data == ">":
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data == "-":
- self.currentToken["data"] += "--!"
- self.state = self.commentEndDashState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["data"] += "--!\uFFFD"
- self.state = self.commentState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-comment-end-bang-state"})
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.currentToken["data"] += "--!" + data
- self.state = self.commentState
- return True
-
- def doctypeState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- self.state = self.beforeDoctypeNameState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-doctype-name-but-got-eof"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "need-space-after-doctype"})
- self.stream.unget(data)
- self.state = self.beforeDoctypeNameState
- return True
-
- def beforeDoctypeNameState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- pass
- elif data == ">":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-doctype-name-but-got-right-bracket"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["name"] = "\uFFFD"
- self.state = self.doctypeNameState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-doctype-name-but-got-eof"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.currentToken["name"] = data
- self.state = self.doctypeNameState
- return True
-
- def doctypeNameState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- self.currentToken["name"] = self.currentToken["name"].translate(asciiUpper2Lower)
- self.state = self.afterDoctypeNameState
- elif data == ">":
- self.currentToken["name"] = self.currentToken["name"].translate(asciiUpper2Lower)
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["name"] += "\uFFFD"
- self.state = self.doctypeNameState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-doctype-name"})
- self.currentToken["correct"] = False
- self.currentToken["name"] = self.currentToken["name"].translate(asciiUpper2Lower)
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.currentToken["name"] += data
- return True
-
- def afterDoctypeNameState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- pass
- elif data == ">":
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data is EOF:
- self.currentToken["correct"] = False
- self.stream.unget(data)
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-doctype"})
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- if data in ("p", "P"):
- matched = True
- for expected in (("u", "U"), ("b", "B"), ("l", "L"),
- ("i", "I"), ("c", "C")):
- data = self.stream.char()
- if data not in expected:
- matched = False
- break
- if matched:
- self.state = self.afterDoctypePublicKeywordState
- return True
- elif data in ("s", "S"):
- matched = True
- for expected in (("y", "Y"), ("s", "S"), ("t", "T"),
- ("e", "E"), ("m", "M")):
- data = self.stream.char()
- if data not in expected:
- matched = False
- break
- if matched:
- self.state = self.afterDoctypeSystemKeywordState
- return True
-
- # All the characters read before the current 'data' will be
- # [a-zA-Z], so they're garbage in the bogus doctype and can be
- # discarded; only the latest character might be '>' or EOF
- # and needs to be ungetted
- self.stream.unget(data)
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "expected-space-or-right-bracket-in-doctype", "datavars":
- {"data": data}})
- self.currentToken["correct"] = False
- self.state = self.bogusDoctypeState
-
- return True
-
- def afterDoctypePublicKeywordState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- self.state = self.beforeDoctypePublicIdentifierState
- elif data in ("'", '"'):
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-char-in-doctype"})
- self.stream.unget(data)
- self.state = self.beforeDoctypePublicIdentifierState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.stream.unget(data)
- self.state = self.beforeDoctypePublicIdentifierState
- return True
-
- def beforeDoctypePublicIdentifierState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- pass
- elif data == "\"":
- self.currentToken["publicId"] = ""
- self.state = self.doctypePublicIdentifierDoubleQuotedState
- elif data == "'":
- self.currentToken["publicId"] = ""
- self.state = self.doctypePublicIdentifierSingleQuotedState
- elif data == ">":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-end-of-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-char-in-doctype"})
- self.currentToken["correct"] = False
- self.state = self.bogusDoctypeState
- return True
-
- def doctypePublicIdentifierDoubleQuotedState(self):
- data = self.stream.char()
- if data == "\"":
- self.state = self.afterDoctypePublicIdentifierState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["publicId"] += "\uFFFD"
- elif data == ">":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-end-of-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.currentToken["publicId"] += data
- return True
-
- def doctypePublicIdentifierSingleQuotedState(self):
- data = self.stream.char()
- if data == "'":
- self.state = self.afterDoctypePublicIdentifierState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["publicId"] += "\uFFFD"
- elif data == ">":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-end-of-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.currentToken["publicId"] += data
- return True
-
- def afterDoctypePublicIdentifierState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- self.state = self.betweenDoctypePublicAndSystemIdentifiersState
- elif data == ">":
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data == '"':
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-char-in-doctype"})
- self.currentToken["systemId"] = ""
- self.state = self.doctypeSystemIdentifierDoubleQuotedState
- elif data == "'":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-char-in-doctype"})
- self.currentToken["systemId"] = ""
- self.state = self.doctypeSystemIdentifierSingleQuotedState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-char-in-doctype"})
- self.currentToken["correct"] = False
- self.state = self.bogusDoctypeState
- return True
-
- def betweenDoctypePublicAndSystemIdentifiersState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- pass
- elif data == ">":
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data == '"':
- self.currentToken["systemId"] = ""
- self.state = self.doctypeSystemIdentifierDoubleQuotedState
- elif data == "'":
- self.currentToken["systemId"] = ""
- self.state = self.doctypeSystemIdentifierSingleQuotedState
- elif data == EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-char-in-doctype"})
- self.currentToken["correct"] = False
- self.state = self.bogusDoctypeState
- return True
-
- def afterDoctypeSystemKeywordState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- self.state = self.beforeDoctypeSystemIdentifierState
- elif data in ("'", '"'):
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-char-in-doctype"})
- self.stream.unget(data)
- self.state = self.beforeDoctypeSystemIdentifierState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.stream.unget(data)
- self.state = self.beforeDoctypeSystemIdentifierState
- return True
-
- def beforeDoctypeSystemIdentifierState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- pass
- elif data == "\"":
- self.currentToken["systemId"] = ""
- self.state = self.doctypeSystemIdentifierDoubleQuotedState
- elif data == "'":
- self.currentToken["systemId"] = ""
- self.state = self.doctypeSystemIdentifierSingleQuotedState
- elif data == ">":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-char-in-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-char-in-doctype"})
- self.currentToken["correct"] = False
- self.state = self.bogusDoctypeState
- return True
-
- def doctypeSystemIdentifierDoubleQuotedState(self):
- data = self.stream.char()
- if data == "\"":
- self.state = self.afterDoctypeSystemIdentifierState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["systemId"] += "\uFFFD"
- elif data == ">":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-end-of-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.currentToken["systemId"] += data
- return True
-
- def doctypeSystemIdentifierSingleQuotedState(self):
- data = self.stream.char()
- if data == "'":
- self.state = self.afterDoctypeSystemIdentifierState
- elif data == "\u0000":
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- self.currentToken["systemId"] += "\uFFFD"
- elif data == ">":
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-end-of-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.currentToken["systemId"] += data
- return True
-
- def afterDoctypeSystemIdentifierState(self):
- data = self.stream.char()
- if data in spaceCharacters:
- pass
- elif data == ">":
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data is EOF:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "eof-in-doctype"})
- self.currentToken["correct"] = False
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- self.tokenQueue.append({"type": tokenTypes["ParseError"], "data":
- "unexpected-char-in-doctype"})
- self.state = self.bogusDoctypeState
- return True
-
- def bogusDoctypeState(self):
- data = self.stream.char()
- if data == ">":
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- elif data is EOF:
- # XXX EMIT
- self.stream.unget(data)
- self.tokenQueue.append(self.currentToken)
- self.state = self.dataState
- else:
- pass
- return True
-
- def cdataSectionState(self):
- data = []
- while True:
- data.append(self.stream.charsUntil("]"))
- data.append(self.stream.charsUntil(">"))
- char = self.stream.char()
- if char == EOF:
- break
- else:
- assert char == ">"
- if data[-1][-2:] == "]]":
- data[-1] = data[-1][:-2]
- break
- else:
- data.append(char)
-
- data = "".join(data) # pylint:disable=redefined-variable-type
- # Deal with null here rather than in the parser
- nullCount = data.count("\u0000")
- if nullCount > 0:
- for _ in range(nullCount):
- self.tokenQueue.append({"type": tokenTypes["ParseError"],
- "data": "invalid-codepoint"})
- data = data.replace("\u0000", "\uFFFD")
- if data:
- self.tokenQueue.append({"type": tokenTypes["Characters"],
- "data": data})
- self.state = self.dataState
- return True
diff --git a/src/pip/_vendor/html5lib/_trie/__init__.py b/src/pip/_vendor/html5lib/_trie/__init__.py
deleted file mode 100644
index 07bad5d31..000000000
--- a/src/pip/_vendor/html5lib/_trie/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-from .py import Trie
-
-__all__ = ["Trie"]
diff --git a/src/pip/_vendor/html5lib/_trie/_base.py b/src/pip/_vendor/html5lib/_trie/_base.py
deleted file mode 100644
index 6b71975f0..000000000
--- a/src/pip/_vendor/html5lib/_trie/_base.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-try:
- from collections.abc import Mapping
-except ImportError: # Python 2.7
- from collections import Mapping
-
-
-class Trie(Mapping):
- """Abstract base class for tries"""
-
- def keys(self, prefix=None):
- # pylint:disable=arguments-differ
- keys = super(Trie, self).keys()
-
- if prefix is None:
- return set(keys)
-
- return {x for x in keys if x.startswith(prefix)}
-
- def has_keys_with_prefix(self, prefix):
- for key in self.keys():
- if key.startswith(prefix):
- return True
-
- return False
-
- def longest_prefix(self, prefix):
- if prefix in self:
- return prefix
-
- for i in range(1, len(prefix) + 1):
- if prefix[:-i] in self:
- return prefix[:-i]
-
- raise KeyError(prefix)
-
- def longest_prefix_item(self, prefix):
- lprefix = self.longest_prefix(prefix)
- return (lprefix, self[lprefix])
diff --git a/src/pip/_vendor/html5lib/_trie/py.py b/src/pip/_vendor/html5lib/_trie/py.py
deleted file mode 100644
index c178b219d..000000000
--- a/src/pip/_vendor/html5lib/_trie/py.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-from pip._vendor.six import text_type
-
-from bisect import bisect_left
-
-from ._base import Trie as ABCTrie
-
-
-class Trie(ABCTrie):
- def __init__(self, data):
- if not all(isinstance(x, text_type) for x in data.keys()):
- raise TypeError("All keys must be strings")
-
- self._data = data
- self._keys = sorted(data.keys())
- self._cachestr = ""
- self._cachepoints = (0, len(data))
-
- def __contains__(self, key):
- return key in self._data
-
- def __len__(self):
- return len(self._data)
-
- def __iter__(self):
- return iter(self._data)
-
- def __getitem__(self, key):
- return self._data[key]
-
- def keys(self, prefix=None):
- if prefix is None or prefix == "" or not self._keys:
- return set(self._keys)
-
- if prefix.startswith(self._cachestr):
- lo, hi = self._cachepoints
- start = i = bisect_left(self._keys, prefix, lo, hi)
- else:
- start = i = bisect_left(self._keys, prefix)
-
- keys = set()
- if start == len(self._keys):
- return keys
-
- while self._keys[i].startswith(prefix):
- keys.add(self._keys[i])
- i += 1
-
- self._cachestr = prefix
- self._cachepoints = (start, i)
-
- return keys
-
- def has_keys_with_prefix(self, prefix):
- if prefix in self._data:
- return True
-
- if prefix.startswith(self._cachestr):
- lo, hi = self._cachepoints
- i = bisect_left(self._keys, prefix, lo, hi)
- else:
- i = bisect_left(self._keys, prefix)
-
- if i == len(self._keys):
- return False
-
- return self._keys[i].startswith(prefix)
diff --git a/src/pip/_vendor/html5lib/_utils.py b/src/pip/_vendor/html5lib/_utils.py
deleted file mode 100644
index d7c4926af..000000000
--- a/src/pip/_vendor/html5lib/_utils.py
+++ /dev/null
@@ -1,159 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-from types import ModuleType
-
-try:
- from collections.abc import Mapping
-except ImportError:
- from collections import Mapping
-
-from pip._vendor.six import text_type, PY3
-
-if PY3:
- import xml.etree.ElementTree as default_etree
-else:
- try:
- import xml.etree.cElementTree as default_etree
- except ImportError:
- import xml.etree.ElementTree as default_etree
-
-
-__all__ = ["default_etree", "MethodDispatcher", "isSurrogatePair",
- "surrogatePairToCodepoint", "moduleFactoryFactory",
- "supports_lone_surrogates"]
-
-
-# Platforms not supporting lone surrogates (\uD800-\uDFFF) should be
-# caught by the below test. In general this would be any platform
-# using UTF-16 as its encoding of unicode strings, such as
-# Jython. This is because UTF-16 itself is based on the use of such
-# surrogates, and there is no mechanism to further escape such
-# escapes.
-try:
- _x = eval('"\\uD800"') # pylint:disable=eval-used
- if not isinstance(_x, text_type):
- # We need this with u"" because of http://bugs.jython.org/issue2039
- _x = eval('u"\\uD800"') # pylint:disable=eval-used
- assert isinstance(_x, text_type)
-except Exception:
- supports_lone_surrogates = False
-else:
- supports_lone_surrogates = True
-
-
-class MethodDispatcher(dict):
- """Dict with 2 special properties:
-
- On initiation, keys that are lists, sets or tuples are converted to
- multiple keys so accessing any one of the items in the original
- list-like object returns the matching value
-
- md = MethodDispatcher({("foo", "bar"):"baz"})
- md["foo"] == "baz"
-
- A default value which can be set through the default attribute.
- """
-
- def __init__(self, items=()):
- _dictEntries = []
- for name, value in items:
- if isinstance(name, (list, tuple, frozenset, set)):
- for item in name:
- _dictEntries.append((item, value))
- else:
- _dictEntries.append((name, value))
- dict.__init__(self, _dictEntries)
- assert len(self) == len(_dictEntries)
- self.default = None
-
- def __getitem__(self, key):
- return dict.get(self, key, self.default)
-
- def __get__(self, instance, owner=None):
- return BoundMethodDispatcher(instance, self)
-
-
-class BoundMethodDispatcher(Mapping):
- """Wraps a MethodDispatcher, binding its return values to `instance`"""
- def __init__(self, instance, dispatcher):
- self.instance = instance
- self.dispatcher = dispatcher
-
- def __getitem__(self, key):
- # see https://docs.python.org/3/reference/datamodel.html#object.__get__
- # on a function, __get__ is used to bind a function to an instance as a bound method
- return self.dispatcher[key].__get__(self.instance)
-
- def get(self, key, default):
- if key in self.dispatcher:
- return self[key]
- else:
- return default
-
- def __iter__(self):
- return iter(self.dispatcher)
-
- def __len__(self):
- return len(self.dispatcher)
-
- def __contains__(self, key):
- return key in self.dispatcher
-
-
-# Some utility functions to deal with weirdness around UCS2 vs UCS4
-# python builds
-
-def isSurrogatePair(data):
- return (len(data) == 2 and
- ord(data[0]) >= 0xD800 and ord(data[0]) <= 0xDBFF and
- ord(data[1]) >= 0xDC00 and ord(data[1]) <= 0xDFFF)
-
-
-def surrogatePairToCodepoint(data):
- char_val = (0x10000 + (ord(data[0]) - 0xD800) * 0x400 +
- (ord(data[1]) - 0xDC00))
- return char_val
-
-# Module Factory Factory (no, this isn't Java, I know)
-# Here to stop this being duplicated all over the place.
-
-
-def moduleFactoryFactory(factory):
- moduleCache = {}
-
- def moduleFactory(baseModule, *args, **kwargs):
- if isinstance(ModuleType.__name__, type("")):
- name = "_%s_factory" % baseModule.__name__
- else:
- name = b"_%s_factory" % baseModule.__name__
-
- kwargs_tuple = tuple(kwargs.items())
-
- try:
- return moduleCache[name][args][kwargs_tuple]
- except KeyError:
- mod = ModuleType(name)
- objs = factory(baseModule, *args, **kwargs)
- mod.__dict__.update(objs)
- if "name" not in moduleCache:
- moduleCache[name] = {}
- if "args" not in moduleCache[name]:
- moduleCache[name][args] = {}
- if "kwargs" not in moduleCache[name][args]:
- moduleCache[name][args][kwargs_tuple] = {}
- moduleCache[name][args][kwargs_tuple] = mod
- return mod
-
- return moduleFactory
-
-
-def memoize(func):
- cache = {}
-
- def wrapped(*args, **kwargs):
- key = (tuple(args), tuple(kwargs.items()))
- if key not in cache:
- cache[key] = func(*args, **kwargs)
- return cache[key]
-
- return wrapped
diff --git a/src/pip/_vendor/html5lib/constants.py b/src/pip/_vendor/html5lib/constants.py
deleted file mode 100644
index fe3e237cd..000000000
--- a/src/pip/_vendor/html5lib/constants.py
+++ /dev/null
@@ -1,2946 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-import string
-
-EOF = None
-
-E = {
- "null-character":
- "Null character in input stream, replaced with U+FFFD.",
- "invalid-codepoint":
- "Invalid codepoint in stream.",
- "incorrectly-placed-solidus":
- "Solidus (/) incorrectly placed in tag.",
- "incorrect-cr-newline-entity":
- "Incorrect CR newline entity, replaced with LF.",
- "illegal-windows-1252-entity":
- "Entity used with illegal number (windows-1252 reference).",
- "cant-convert-numeric-entity":
- "Numeric entity couldn't be converted to character "
- "(codepoint U+%(charAsInt)08x).",
- "illegal-codepoint-for-numeric-entity":
- "Numeric entity represents an illegal codepoint: "
- "U+%(charAsInt)08x.",
- "numeric-entity-without-semicolon":
- "Numeric entity didn't end with ';'.",
- "expected-numeric-entity-but-got-eof":
- "Numeric entity expected. Got end of file instead.",
- "expected-numeric-entity":
- "Numeric entity expected but none found.",
- "named-entity-without-semicolon":
- "Named entity didn't end with ';'.",
- "expected-named-entity":
- "Named entity expected. Got none.",
- "attributes-in-end-tag":
- "End tag contains unexpected attributes.",
- 'self-closing-flag-on-end-tag':
- "End tag contains unexpected self-closing flag.",
- "expected-tag-name-but-got-right-bracket":
- "Expected tag name. Got '>' instead.",
- "expected-tag-name-but-got-question-mark":
- "Expected tag name. Got '?' instead. (HTML doesn't "
- "support processing instructions.)",
- "expected-tag-name":
- "Expected tag name. Got something else instead",
- "expected-closing-tag-but-got-right-bracket":
- "Expected closing tag. Got '>' instead. Ignoring '</>'.",
- "expected-closing-tag-but-got-eof":
- "Expected closing tag. Unexpected end of file.",
- "expected-closing-tag-but-got-char":
- "Expected closing tag. Unexpected character '%(data)s' found.",
- "eof-in-tag-name":
- "Unexpected end of file in the tag name.",
- "expected-attribute-name-but-got-eof":
- "Unexpected end of file. Expected attribute name instead.",
- "eof-in-attribute-name":
- "Unexpected end of file in attribute name.",
- "invalid-character-in-attribute-name":
- "Invalid character in attribute name",
- "duplicate-attribute":
- "Dropped duplicate attribute on tag.",
- "expected-end-of-tag-name-but-got-eof":
- "Unexpected end of file. Expected = or end of tag.",
- "expected-attribute-value-but-got-eof":
- "Unexpected end of file. Expected attribute value.",
- "expected-attribute-value-but-got-right-bracket":
- "Expected attribute value. Got '>' instead.",
- 'equals-in-unquoted-attribute-value':
- "Unexpected = in unquoted attribute",
- 'unexpected-character-in-unquoted-attribute-value':
- "Unexpected character in unquoted attribute",
- "invalid-character-after-attribute-name":
- "Unexpected character after attribute name.",
- "unexpected-character-after-attribute-value":
- "Unexpected character after attribute value.",
- "eof-in-attribute-value-double-quote":
- "Unexpected end of file in attribute value (\").",
- "eof-in-attribute-value-single-quote":
- "Unexpected end of file in attribute value (').",
- "eof-in-attribute-value-no-quotes":
- "Unexpected end of file in attribute value.",
- "unexpected-EOF-after-solidus-in-tag":
- "Unexpected end of file in tag. Expected >",
- "unexpected-character-after-solidus-in-tag":
- "Unexpected character after / in tag. Expected >",
- "expected-dashes-or-doctype":
- "Expected '--' or 'DOCTYPE'. Not found.",
- "unexpected-bang-after-double-dash-in-comment":
- "Unexpected ! after -- in comment",
- "unexpected-space-after-double-dash-in-comment":
- "Unexpected space after -- in comment",
- "incorrect-comment":
- "Incorrect comment.",
- "eof-in-comment":
- "Unexpected end of file in comment.",
- "eof-in-comment-end-dash":
- "Unexpected end of file in comment (-)",
- "unexpected-dash-after-double-dash-in-comment":
- "Unexpected '-' after '--' found in comment.",
- "eof-in-comment-double-dash":
- "Unexpected end of file in comment (--).",
- "eof-in-comment-end-space-state":
- "Unexpected end of file in comment.",
- "eof-in-comment-end-bang-state":
- "Unexpected end of file in comment.",
- "unexpected-char-in-comment":
- "Unexpected character in comment found.",
- "need-space-after-doctype":
- "No space after literal string 'DOCTYPE'.",
- "expected-doctype-name-but-got-right-bracket":
- "Unexpected > character. Expected DOCTYPE name.",
- "expected-doctype-name-but-got-eof":
- "Unexpected end of file. Expected DOCTYPE name.",
- "eof-in-doctype-name":
- "Unexpected end of file in DOCTYPE name.",
- "eof-in-doctype":
- "Unexpected end of file in DOCTYPE.",
- "expected-space-or-right-bracket-in-doctype":
- "Expected space or '>'. Got '%(data)s'",
- "unexpected-end-of-doctype":
- "Unexpected end of DOCTYPE.",
- "unexpected-char-in-doctype":
- "Unexpected character in DOCTYPE.",
- "eof-in-innerhtml":
- "XXX innerHTML EOF",
- "unexpected-doctype":
- "Unexpected DOCTYPE. Ignored.",
- "non-html-root":
- "html needs to be the first start tag.",
- "expected-doctype-but-got-eof":
- "Unexpected End of file. Expected DOCTYPE.",
- "unknown-doctype":
- "Erroneous DOCTYPE.",
- "expected-doctype-but-got-chars":
- "Unexpected non-space characters. Expected DOCTYPE.",
- "expected-doctype-but-got-start-tag":
- "Unexpected start tag (%(name)s). Expected DOCTYPE.",
- "expected-doctype-but-got-end-tag":
- "Unexpected end tag (%(name)s). Expected DOCTYPE.",
- "end-tag-after-implied-root":
- "Unexpected end tag (%(name)s) after the (implied) root element.",
- "expected-named-closing-tag-but-got-eof":
- "Unexpected end of file. Expected end tag (%(name)s).",
- "two-heads-are-not-better-than-one":
- "Unexpected start tag head in existing head. Ignored.",
- "unexpected-end-tag":
- "Unexpected end tag (%(name)s). Ignored.",
- "unexpected-start-tag-out-of-my-head":
- "Unexpected start tag (%(name)s) that can be in head. Moved.",
- "unexpected-start-tag":
- "Unexpected start tag (%(name)s).",
- "missing-end-tag":
- "Missing end tag (%(name)s).",
- "missing-end-tags":
- "Missing end tags (%(name)s).",
- "unexpected-start-tag-implies-end-tag":
- "Unexpected start tag (%(startName)s) "
- "implies end tag (%(endName)s).",
- "unexpected-start-tag-treated-as":
- "Unexpected start tag (%(originalName)s). Treated as %(newName)s.",
- "deprecated-tag":
- "Unexpected start tag %(name)s. Don't use it!",
- "unexpected-start-tag-ignored":
- "Unexpected start tag %(name)s. Ignored.",
- "expected-one-end-tag-but-got-another":
- "Unexpected end tag (%(gotName)s). "
- "Missing end tag (%(expectedName)s).",
- "end-tag-too-early":
- "End tag (%(name)s) seen too early. Expected other end tag.",
- "end-tag-too-early-named":
- "Unexpected end tag (%(gotName)s). Expected end tag (%(expectedName)s).",
- "end-tag-too-early-ignored":
- "End tag (%(name)s) seen too early. Ignored.",
- "adoption-agency-1.1":
- "End tag (%(name)s) violates step 1, "
- "paragraph 1 of the adoption agency algorithm.",
- "adoption-agency-1.2":
- "End tag (%(name)s) violates step 1, "
- "paragraph 2 of the adoption agency algorithm.",
- "adoption-agency-1.3":
- "End tag (%(name)s) violates step 1, "
- "paragraph 3 of the adoption agency algorithm.",
- "adoption-agency-4.4":
- "End tag (%(name)s) violates step 4, "
- "paragraph 4 of the adoption agency algorithm.",
- "unexpected-end-tag-treated-as":
- "Unexpected end tag (%(originalName)s). Treated as %(newName)s.",
- "no-end-tag":
- "This element (%(name)s) has no end tag.",
- "unexpected-implied-end-tag-in-table":
- "Unexpected implied end tag (%(name)s) in the table phase.",
- "unexpected-implied-end-tag-in-table-body":
- "Unexpected implied end tag (%(name)s) in the table body phase.",
- "unexpected-char-implies-table-voodoo":
- "Unexpected non-space characters in "
- "table context caused voodoo mode.",
- "unexpected-hidden-input-in-table":
- "Unexpected input with type hidden in table context.",
- "unexpected-form-in-table":
- "Unexpected form in table context.",
- "unexpected-start-tag-implies-table-voodoo":
- "Unexpected start tag (%(name)s) in "
- "table context caused voodoo mode.",
- "unexpected-end-tag-implies-table-voodoo":
- "Unexpected end tag (%(name)s) in "
- "table context caused voodoo mode.",
- "unexpected-cell-in-table-body":
- "Unexpected table cell start tag (%(name)s) "
- "in the table body phase.",
- "unexpected-cell-end-tag":
- "Got table cell end tag (%(name)s) "
- "while required end tags are missing.",
- "unexpected-end-tag-in-table-body":
- "Unexpected end tag (%(name)s) in the table body phase. Ignored.",
- "unexpected-implied-end-tag-in-table-row":
- "Unexpected implied end tag (%(name)s) in the table row phase.",
- "unexpected-end-tag-in-table-row":
- "Unexpected end tag (%(name)s) in the table row phase. Ignored.",
- "unexpected-select-in-select":
- "Unexpected select start tag in the select phase "
- "treated as select end tag.",
- "unexpected-input-in-select":
- "Unexpected input start tag in the select phase.",
- "unexpected-start-tag-in-select":
- "Unexpected start tag token (%(name)s in the select phase. "
- "Ignored.",
- "unexpected-end-tag-in-select":
- "Unexpected end tag (%(name)s) in the select phase. Ignored.",
- "unexpected-table-element-start-tag-in-select-in-table":
- "Unexpected table element start tag (%(name)s) in the select in table phase.",
- "unexpected-table-element-end-tag-in-select-in-table":
- "Unexpected table element end tag (%(name)s) in the select in table phase.",
- "unexpected-char-after-body":
- "Unexpected non-space characters in the after body phase.",
- "unexpected-start-tag-after-body":
- "Unexpected start tag token (%(name)s)"
- " in the after body phase.",
- "unexpected-end-tag-after-body":
- "Unexpected end tag token (%(name)s)"
- " in the after body phase.",
- "unexpected-char-in-frameset":
- "Unexpected characters in the frameset phase. Characters ignored.",
- "unexpected-start-tag-in-frameset":
- "Unexpected start tag token (%(name)s)"
- " in the frameset phase. Ignored.",
- "unexpected-frameset-in-frameset-innerhtml":
- "Unexpected end tag token (frameset) "
- "in the frameset phase (innerHTML).",
- "unexpected-end-tag-in-frameset":
- "Unexpected end tag token (%(name)s)"
- " in the frameset phase. Ignored.",
- "unexpected-char-after-frameset":
- "Unexpected non-space characters in the "
- "after frameset phase. Ignored.",
- "unexpected-start-tag-after-frameset":
- "Unexpected start tag (%(name)s)"
- " in the after frameset phase. Ignored.",
- "unexpected-end-tag-after-frameset":
- "Unexpected end tag (%(name)s)"
- " in the after frameset phase. Ignored.",
- "unexpected-end-tag-after-body-innerhtml":
- "Unexpected end tag after body(innerHtml)",
- "expected-eof-but-got-char":
- "Unexpected non-space characters. Expected end of file.",
- "expected-eof-but-got-start-tag":
- "Unexpected start tag (%(name)s)"
- ". Expected end of file.",
- "expected-eof-but-got-end-tag":
- "Unexpected end tag (%(name)s)"
- ". Expected end of file.",
- "eof-in-table":
- "Unexpected end of file. Expected table content.",
- "eof-in-select":
- "Unexpected end of file. Expected select content.",
- "eof-in-frameset":
- "Unexpected end of file. Expected frameset content.",
- "eof-in-script-in-script":
- "Unexpected end of file. Expected script content.",
- "eof-in-foreign-lands":
- "Unexpected end of file. Expected foreign content",
- "non-void-element-with-trailing-solidus":
- "Trailing solidus not allowed on element %(name)s",
- "unexpected-html-element-in-foreign-content":
- "Element %(name)s not allowed in a non-html context",
- "unexpected-end-tag-before-html":
- "Unexpected end tag (%(name)s) before html.",
- "unexpected-inhead-noscript-tag":
- "Element %(name)s not allowed in a inhead-noscript context",
- "eof-in-head-noscript":
- "Unexpected end of file. Expected inhead-noscript content",
- "char-in-head-noscript":
- "Unexpected non-space character. Expected inhead-noscript content",
- "XXX-undefined-error":
- "Undefined error (this sucks and should be fixed)",
-}
-
-namespaces = {
- "html": "http://www.w3.org/1999/xhtml",
- "mathml": "http://www.w3.org/1998/Math/MathML",
- "svg": "http://www.w3.org/2000/svg",
- "xlink": "http://www.w3.org/1999/xlink",
- "xml": "http://www.w3.org/XML/1998/namespace",
- "xmlns": "http://www.w3.org/2000/xmlns/"
-}
-
-scopingElements = frozenset([
- (namespaces["html"], "applet"),
- (namespaces["html"], "caption"),
- (namespaces["html"], "html"),
- (namespaces["html"], "marquee"),
- (namespaces["html"], "object"),
- (namespaces["html"], "table"),
- (namespaces["html"], "td"),
- (namespaces["html"], "th"),
- (namespaces["mathml"], "mi"),
- (namespaces["mathml"], "mo"),
- (namespaces["mathml"], "mn"),
- (namespaces["mathml"], "ms"),
- (namespaces["mathml"], "mtext"),
- (namespaces["mathml"], "annotation-xml"),
- (namespaces["svg"], "foreignObject"),
- (namespaces["svg"], "desc"),
- (namespaces["svg"], "title"),
-])
-
-formattingElements = frozenset([
- (namespaces["html"], "a"),
- (namespaces["html"], "b"),
- (namespaces["html"], "big"),
- (namespaces["html"], "code"),
- (namespaces["html"], "em"),
- (namespaces["html"], "font"),
- (namespaces["html"], "i"),
- (namespaces["html"], "nobr"),
- (namespaces["html"], "s"),
- (namespaces["html"], "small"),
- (namespaces["html"], "strike"),
- (namespaces["html"], "strong"),
- (namespaces["html"], "tt"),
- (namespaces["html"], "u")
-])
-
-specialElements = frozenset([
- (namespaces["html"], "address"),
- (namespaces["html"], "applet"),
- (namespaces["html"], "area"),
- (namespaces["html"], "article"),
- (namespaces["html"], "aside"),
- (namespaces["html"], "base"),
- (namespaces["html"], "basefont"),
- (namespaces["html"], "bgsound"),
- (namespaces["html"], "blockquote"),
- (namespaces["html"], "body"),
- (namespaces["html"], "br"),
- (namespaces["html"], "button"),
- (namespaces["html"], "caption"),
- (namespaces["html"], "center"),
- (namespaces["html"], "col"),
- (namespaces["html"], "colgroup"),
- (namespaces["html"], "command"),
- (namespaces["html"], "dd"),
- (namespaces["html"], "details"),
- (namespaces["html"], "dir"),
- (namespaces["html"], "div"),
- (namespaces["html"], "dl"),
- (namespaces["html"], "dt"),
- (namespaces["html"], "embed"),
- (namespaces["html"], "fieldset"),
- (namespaces["html"], "figure"),
- (namespaces["html"], "footer"),
- (namespaces["html"], "form"),
- (namespaces["html"], "frame"),
- (namespaces["html"], "frameset"),
- (namespaces["html"], "h1"),
- (namespaces["html"], "h2"),
- (namespaces["html"], "h3"),
- (namespaces["html"], "h4"),
- (namespaces["html"], "h5"),
- (namespaces["html"], "h6"),
- (namespaces["html"], "head"),
- (namespaces["html"], "header"),
- (namespaces["html"], "hr"),
- (namespaces["html"], "html"),
- (namespaces["html"], "iframe"),
- # Note that image is commented out in the spec as "this isn't an
- # element that can end up on the stack, so it doesn't matter,"
- (namespaces["html"], "image"),
- (namespaces["html"], "img"),
- (namespaces["html"], "input"),
- (namespaces["html"], "isindex"),
- (namespaces["html"], "li"),
- (namespaces["html"], "link"),
- (namespaces["html"], "listing"),
- (namespaces["html"], "marquee"),
- (namespaces["html"], "menu"),
- (namespaces["html"], "meta"),
- (namespaces["html"], "nav"),
- (namespaces["html"], "noembed"),
- (namespaces["html"], "noframes"),
- (namespaces["html"], "noscript"),
- (namespaces["html"], "object"),
- (namespaces["html"], "ol"),
- (namespaces["html"], "p"),
- (namespaces["html"], "param"),
- (namespaces["html"], "plaintext"),
- (namespaces["html"], "pre"),
- (namespaces["html"], "script"),
- (namespaces["html"], "section"),
- (namespaces["html"], "select"),
- (namespaces["html"], "style"),
- (namespaces["html"], "table"),
- (namespaces["html"], "tbody"),
- (namespaces["html"], "td"),
- (namespaces["html"], "textarea"),
- (namespaces["html"], "tfoot"),
- (namespaces["html"], "th"),
- (namespaces["html"], "thead"),
- (namespaces["html"], "title"),
- (namespaces["html"], "tr"),
- (namespaces["html"], "ul"),
- (namespaces["html"], "wbr"),
- (namespaces["html"], "xmp"),
- (namespaces["svg"], "foreignObject")
-])
-
-htmlIntegrationPointElements = frozenset([
- (namespaces["mathml"], "annotation-xml"),
- (namespaces["svg"], "foreignObject"),
- (namespaces["svg"], "desc"),
- (namespaces["svg"], "title")
-])
-
-mathmlTextIntegrationPointElements = frozenset([
- (namespaces["mathml"], "mi"),
- (namespaces["mathml"], "mo"),
- (namespaces["mathml"], "mn"),
- (namespaces["mathml"], "ms"),
- (namespaces["mathml"], "mtext")
-])
-
-adjustSVGAttributes = {
- "attributename": "attributeName",
- "attributetype": "attributeType",
- "basefrequency": "baseFrequency",
- "baseprofile": "baseProfile",
- "calcmode": "calcMode",
- "clippathunits": "clipPathUnits",
- "contentscripttype": "contentScriptType",
- "contentstyletype": "contentStyleType",
- "diffuseconstant": "diffuseConstant",
- "edgemode": "edgeMode",
- "externalresourcesrequired": "externalResourcesRequired",
- "filterres": "filterRes",
- "filterunits": "filterUnits",
- "glyphref": "glyphRef",
- "gradienttransform": "gradientTransform",
- "gradientunits": "gradientUnits",
- "kernelmatrix": "kernelMatrix",
- "kernelunitlength": "kernelUnitLength",
- "keypoints": "keyPoints",
- "keysplines": "keySplines",
- "keytimes": "keyTimes",
- "lengthadjust": "lengthAdjust",
- "limitingconeangle": "limitingConeAngle",
- "markerheight": "markerHeight",
- "markerunits": "markerUnits",
- "markerwidth": "markerWidth",
- "maskcontentunits": "maskContentUnits",
- "maskunits": "maskUnits",
- "numoctaves": "numOctaves",
- "pathlength": "pathLength",
- "patterncontentunits": "patternContentUnits",
- "patterntransform": "patternTransform",
- "patternunits": "patternUnits",
- "pointsatx": "pointsAtX",
- "pointsaty": "pointsAtY",
- "pointsatz": "pointsAtZ",
- "preservealpha": "preserveAlpha",
- "preserveaspectratio": "preserveAspectRatio",
- "primitiveunits": "primitiveUnits",
- "refx": "refX",
- "refy": "refY",
- "repeatcount": "repeatCount",
- "repeatdur": "repeatDur",
- "requiredextensions": "requiredExtensions",
- "requiredfeatures": "requiredFeatures",
- "specularconstant": "specularConstant",
- "specularexponent": "specularExponent",
- "spreadmethod": "spreadMethod",
- "startoffset": "startOffset",
- "stddeviation": "stdDeviation",
- "stitchtiles": "stitchTiles",
- "surfacescale": "surfaceScale",
- "systemlanguage": "systemLanguage",
- "tablevalues": "tableValues",
- "targetx": "targetX",
- "targety": "targetY",
- "textlength": "textLength",
- "viewbox": "viewBox",
- "viewtarget": "viewTarget",
- "xchannelselector": "xChannelSelector",
- "ychannelselector": "yChannelSelector",
- "zoomandpan": "zoomAndPan"
-}
-
-adjustMathMLAttributes = {"definitionurl": "definitionURL"}
-
-adjustForeignAttributes = {
- "xlink:actuate": ("xlink", "actuate", namespaces["xlink"]),
- "xlink:arcrole": ("xlink", "arcrole", namespaces["xlink"]),
- "xlink:href": ("xlink", "href", namespaces["xlink"]),
- "xlink:role": ("xlink", "role", namespaces["xlink"]),
- "xlink:show": ("xlink", "show", namespaces["xlink"]),
- "xlink:title": ("xlink", "title", namespaces["xlink"]),
- "xlink:type": ("xlink", "type", namespaces["xlink"]),
- "xml:base": ("xml", "base", namespaces["xml"]),
- "xml:lang": ("xml", "lang", namespaces["xml"]),
- "xml:space": ("xml", "space", namespaces["xml"]),
- "xmlns": (None, "xmlns", namespaces["xmlns"]),
- "xmlns:xlink": ("xmlns", "xlink", namespaces["xmlns"])
-}
-
-unadjustForeignAttributes = {(ns, local): qname for qname, (prefix, local, ns) in
- adjustForeignAttributes.items()}
-
-spaceCharacters = frozenset([
- "\t",
- "\n",
- "\u000C",
- " ",
- "\r"
-])
-
-tableInsertModeElements = frozenset([
- "table",
- "tbody",
- "tfoot",
- "thead",
- "tr"
-])
-
-asciiLowercase = frozenset(string.ascii_lowercase)
-asciiUppercase = frozenset(string.ascii_uppercase)
-asciiLetters = frozenset(string.ascii_letters)
-digits = frozenset(string.digits)
-hexDigits = frozenset(string.hexdigits)
-
-asciiUpper2Lower = {ord(c): ord(c.lower()) for c in string.ascii_uppercase}
-
-# Heading elements need to be ordered
-headingElements = (
- "h1",
- "h2",
- "h3",
- "h4",
- "h5",
- "h6"
-)
-
-voidElements = frozenset([
- "base",
- "command",
- "event-source",
- "link",
- "meta",
- "hr",
- "br",
- "img",
- "embed",
- "param",
- "area",
- "col",
- "input",
- "source",
- "track"
-])
-
-cdataElements = frozenset(['title', 'textarea'])
-
-rcdataElements = frozenset([
- 'style',
- 'script',
- 'xmp',
- 'iframe',
- 'noembed',
- 'noframes',
- 'noscript'
-])
-
-booleanAttributes = {
- "": frozenset(["irrelevant", "itemscope"]),
- "style": frozenset(["scoped"]),
- "img": frozenset(["ismap"]),
- "audio": frozenset(["autoplay", "controls"]),
- "video": frozenset(["autoplay", "controls"]),
- "script": frozenset(["defer", "async"]),
- "details": frozenset(["open"]),
- "datagrid": frozenset(["multiple", "disabled"]),
- "command": frozenset(["hidden", "disabled", "checked", "default"]),
- "hr": frozenset(["noshade"]),
- "menu": frozenset(["autosubmit"]),
- "fieldset": frozenset(["disabled", "readonly"]),
- "option": frozenset(["disabled", "readonly", "selected"]),
- "optgroup": frozenset(["disabled", "readonly"]),
- "button": frozenset(["disabled", "autofocus"]),
- "input": frozenset(["disabled", "readonly", "required", "autofocus", "checked", "ismap"]),
- "select": frozenset(["disabled", "readonly", "autofocus", "multiple"]),
- "output": frozenset(["disabled", "readonly"]),
- "iframe": frozenset(["seamless"]),
-}
-
-# entitiesWindows1252 has to be _ordered_ and needs to have an index. It
-# therefore can't be a frozenset.
-entitiesWindows1252 = (
- 8364, # 0x80 0x20AC EURO SIGN
- 65533, # 0x81 UNDEFINED
- 8218, # 0x82 0x201A SINGLE LOW-9 QUOTATION MARK
- 402, # 0x83 0x0192 LATIN SMALL LETTER F WITH HOOK
- 8222, # 0x84 0x201E DOUBLE LOW-9 QUOTATION MARK
- 8230, # 0x85 0x2026 HORIZONTAL ELLIPSIS
- 8224, # 0x86 0x2020 DAGGER
- 8225, # 0x87 0x2021 DOUBLE DAGGER
- 710, # 0x88 0x02C6 MODIFIER LETTER CIRCUMFLEX ACCENT
- 8240, # 0x89 0x2030 PER MILLE SIGN
- 352, # 0x8A 0x0160 LATIN CAPITAL LETTER S WITH CARON
- 8249, # 0x8B 0x2039 SINGLE LEFT-POINTING ANGLE QUOTATION MARK
- 338, # 0x8C 0x0152 LATIN CAPITAL LIGATURE OE
- 65533, # 0x8D UNDEFINED
- 381, # 0x8E 0x017D LATIN CAPITAL LETTER Z WITH CARON
- 65533, # 0x8F UNDEFINED
- 65533, # 0x90 UNDEFINED
- 8216, # 0x91 0x2018 LEFT SINGLE QUOTATION MARK
- 8217, # 0x92 0x2019 RIGHT SINGLE QUOTATION MARK
- 8220, # 0x93 0x201C LEFT DOUBLE QUOTATION MARK
- 8221, # 0x94 0x201D RIGHT DOUBLE QUOTATION MARK
- 8226, # 0x95 0x2022 BULLET
- 8211, # 0x96 0x2013 EN DASH
- 8212, # 0x97 0x2014 EM DASH
- 732, # 0x98 0x02DC SMALL TILDE
- 8482, # 0x99 0x2122 TRADE MARK SIGN
- 353, # 0x9A 0x0161 LATIN SMALL LETTER S WITH CARON
- 8250, # 0x9B 0x203A SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
- 339, # 0x9C 0x0153 LATIN SMALL LIGATURE OE
- 65533, # 0x9D UNDEFINED
- 382, # 0x9E 0x017E LATIN SMALL LETTER Z WITH CARON
- 376 # 0x9F 0x0178 LATIN CAPITAL LETTER Y WITH DIAERESIS
-)
-
-xmlEntities = frozenset(['lt;', 'gt;', 'amp;', 'apos;', 'quot;'])
-
-entities = {
- "AElig": "\xc6",
- "AElig;": "\xc6",
- "AMP": "&",
- "AMP;": "&",
- "Aacute": "\xc1",
- "Aacute;": "\xc1",
- "Abreve;": "\u0102",
- "Acirc": "\xc2",
- "Acirc;": "\xc2",
- "Acy;": "\u0410",
- "Afr;": "\U0001d504",
- "Agrave": "\xc0",
- "Agrave;": "\xc0",
- "Alpha;": "\u0391",
- "Amacr;": "\u0100",
- "And;": "\u2a53",
- "Aogon;": "\u0104",
- "Aopf;": "\U0001d538",
- "ApplyFunction;": "\u2061",
- "Aring": "\xc5",
- "Aring;": "\xc5",
- "Ascr;": "\U0001d49c",
- "Assign;": "\u2254",
- "Atilde": "\xc3",
- "Atilde;": "\xc3",
- "Auml": "\xc4",
- "Auml;": "\xc4",
- "Backslash;": "\u2216",
- "Barv;": "\u2ae7",
- "Barwed;": "\u2306",
- "Bcy;": "\u0411",
- "Because;": "\u2235",
- "Bernoullis;": "\u212c",
- "Beta;": "\u0392",
- "Bfr;": "\U0001d505",
- "Bopf;": "\U0001d539",
- "Breve;": "\u02d8",
- "Bscr;": "\u212c",
- "Bumpeq;": "\u224e",
- "CHcy;": "\u0427",
- "COPY": "\xa9",
- "COPY;": "\xa9",
- "Cacute;": "\u0106",
- "Cap;": "\u22d2",
- "CapitalDifferentialD;": "\u2145",
- "Cayleys;": "\u212d",
- "Ccaron;": "\u010c",
- "Ccedil": "\xc7",
- "Ccedil;": "\xc7",
- "Ccirc;": "\u0108",
- "Cconint;": "\u2230",
- "Cdot;": "\u010a",
- "Cedilla;": "\xb8",
- "CenterDot;": "\xb7",
- "Cfr;": "\u212d",
- "Chi;": "\u03a7",
- "CircleDot;": "\u2299",
- "CircleMinus;": "\u2296",
- "CirclePlus;": "\u2295",
- "CircleTimes;": "\u2297",
- "ClockwiseContourIntegral;": "\u2232",
- "CloseCurlyDoubleQuote;": "\u201d",
- "CloseCurlyQuote;": "\u2019",
- "Colon;": "\u2237",
- "Colone;": "\u2a74",
- "Congruent;": "\u2261",
- "Conint;": "\u222f",
- "ContourIntegral;": "\u222e",
- "Copf;": "\u2102",
- "Coproduct;": "\u2210",
- "CounterClockwiseContourIntegral;": "\u2233",
- "Cross;": "\u2a2f",
- "Cscr;": "\U0001d49e",
- "Cup;": "\u22d3",
- "CupCap;": "\u224d",
- "DD;": "\u2145",
- "DDotrahd;": "\u2911",
- "DJcy;": "\u0402",
- "DScy;": "\u0405",
- "DZcy;": "\u040f",
- "Dagger;": "\u2021",
- "Darr;": "\u21a1",
- "Dashv;": "\u2ae4",
- "Dcaron;": "\u010e",
- "Dcy;": "\u0414",
- "Del;": "\u2207",
- "Delta;": "\u0394",
- "Dfr;": "\U0001d507",
- "DiacriticalAcute;": "\xb4",
- "DiacriticalDot;": "\u02d9",
- "DiacriticalDoubleAcute;": "\u02dd",
- "DiacriticalGrave;": "`",
- "DiacriticalTilde;": "\u02dc",
- "Diamond;": "\u22c4",
- "DifferentialD;": "\u2146",
- "Dopf;": "\U0001d53b",
- "Dot;": "\xa8",
- "DotDot;": "\u20dc",
- "DotEqual;": "\u2250",
- "DoubleContourIntegral;": "\u222f",
- "DoubleDot;": "\xa8",
- "DoubleDownArrow;": "\u21d3",
- "DoubleLeftArrow;": "\u21d0",
- "DoubleLeftRightArrow;": "\u21d4",
- "DoubleLeftTee;": "\u2ae4",
- "DoubleLongLeftArrow;": "\u27f8",
- "DoubleLongLeftRightArrow;": "\u27fa",
- "DoubleLongRightArrow;": "\u27f9",
- "DoubleRightArrow;": "\u21d2",
- "DoubleRightTee;": "\u22a8",
- "DoubleUpArrow;": "\u21d1",
- "DoubleUpDownArrow;": "\u21d5",
- "DoubleVerticalBar;": "\u2225",
- "DownArrow;": "\u2193",
- "DownArrowBar;": "\u2913",
- "DownArrowUpArrow;": "\u21f5",
- "DownBreve;": "\u0311",
- "DownLeftRightVector;": "\u2950",
- "DownLeftTeeVector;": "\u295e",
- "DownLeftVector;": "\u21bd",
- "DownLeftVectorBar;": "\u2956",
- "DownRightTeeVector;": "\u295f",
- "DownRightVector;": "\u21c1",
- "DownRightVectorBar;": "\u2957",
- "DownTee;": "\u22a4",
- "DownTeeArrow;": "\u21a7",
- "Downarrow;": "\u21d3",
- "Dscr;": "\U0001d49f",
- "Dstrok;": "\u0110",
- "ENG;": "\u014a",
- "ETH": "\xd0",
- "ETH;": "\xd0",
- "Eacute": "\xc9",
- "Eacute;": "\xc9",
- "Ecaron;": "\u011a",
- "Ecirc": "\xca",
- "Ecirc;": "\xca",
- "Ecy;": "\u042d",
- "Edot;": "\u0116",
- "Efr;": "\U0001d508",
- "Egrave": "\xc8",
- "Egrave;": "\xc8",
- "Element;": "\u2208",
- "Emacr;": "\u0112",
- "EmptySmallSquare;": "\u25fb",
- "EmptyVerySmallSquare;": "\u25ab",
- "Eogon;": "\u0118",
- "Eopf;": "\U0001d53c",
- "Epsilon;": "\u0395",
- "Equal;": "\u2a75",
- "EqualTilde;": "\u2242",
- "Equilibrium;": "\u21cc",
- "Escr;": "\u2130",
- "Esim;": "\u2a73",
- "Eta;": "\u0397",
- "Euml": "\xcb",
- "Euml;": "\xcb",
- "Exists;": "\u2203",
- "ExponentialE;": "\u2147",
- "Fcy;": "\u0424",
- "Ffr;": "\U0001d509",
- "FilledSmallSquare;": "\u25fc",
- "FilledVerySmallSquare;": "\u25aa",
- "Fopf;": "\U0001d53d",
- "ForAll;": "\u2200",
- "Fouriertrf;": "\u2131",
- "Fscr;": "\u2131",
- "GJcy;": "\u0403",
- "GT": ">",
- "GT;": ">",
- "Gamma;": "\u0393",
- "Gammad;": "\u03dc",
- "Gbreve;": "\u011e",
- "Gcedil;": "\u0122",
- "Gcirc;": "\u011c",
- "Gcy;": "\u0413",
- "Gdot;": "\u0120",
- "Gfr;": "\U0001d50a",
- "Gg;": "\u22d9",
- "Gopf;": "\U0001d53e",
- "GreaterEqual;": "\u2265",
- "GreaterEqualLess;": "\u22db",
- "GreaterFullEqual;": "\u2267",
- "GreaterGreater;": "\u2aa2",
- "GreaterLess;": "\u2277",
- "GreaterSlantEqual;": "\u2a7e",
- "GreaterTilde;": "\u2273",
- "Gscr;": "\U0001d4a2",
- "Gt;": "\u226b",
- "HARDcy;": "\u042a",
- "Hacek;": "\u02c7",
- "Hat;": "^",
- "Hcirc;": "\u0124",
- "Hfr;": "\u210c",
- "HilbertSpace;": "\u210b",
- "Hopf;": "\u210d",
- "HorizontalLine;": "\u2500",
- "Hscr;": "\u210b",
- "Hstrok;": "\u0126",
- "HumpDownHump;": "\u224e",
- "HumpEqual;": "\u224f",
- "IEcy;": "\u0415",
- "IJlig;": "\u0132",
- "IOcy;": "\u0401",
- "Iacute": "\xcd",
- "Iacute;": "\xcd",
- "Icirc": "\xce",
- "Icirc;": "\xce",
- "Icy;": "\u0418",
- "Idot;": "\u0130",
- "Ifr;": "\u2111",
- "Igrave": "\xcc",
- "Igrave;": "\xcc",
- "Im;": "\u2111",
- "Imacr;": "\u012a",
- "ImaginaryI;": "\u2148",
- "Implies;": "\u21d2",
- "Int;": "\u222c",
- "Integral;": "\u222b",
- "Intersection;": "\u22c2",
- "InvisibleComma;": "\u2063",
- "InvisibleTimes;": "\u2062",
- "Iogon;": "\u012e",
- "Iopf;": "\U0001d540",
- "Iota;": "\u0399",
- "Iscr;": "\u2110",
- "Itilde;": "\u0128",
- "Iukcy;": "\u0406",
- "Iuml": "\xcf",
- "Iuml;": "\xcf",
- "Jcirc;": "\u0134",
- "Jcy;": "\u0419",
- "Jfr;": "\U0001d50d",
- "Jopf;": "\U0001d541",
- "Jscr;": "\U0001d4a5",
- "Jsercy;": "\u0408",
- "Jukcy;": "\u0404",
- "KHcy;": "\u0425",
- "KJcy;": "\u040c",
- "Kappa;": "\u039a",
- "Kcedil;": "\u0136",
- "Kcy;": "\u041a",
- "Kfr;": "\U0001d50e",
- "Kopf;": "\U0001d542",
- "Kscr;": "\U0001d4a6",
- "LJcy;": "\u0409",
- "LT": "<",
- "LT;": "<",
- "Lacute;": "\u0139",
- "Lambda;": "\u039b",
- "Lang;": "\u27ea",
- "Laplacetrf;": "\u2112",
- "Larr;": "\u219e",
- "Lcaron;": "\u013d",
- "Lcedil;": "\u013b",
- "Lcy;": "\u041b",
- "LeftAngleBracket;": "\u27e8",
- "LeftArrow;": "\u2190",
- "LeftArrowBar;": "\u21e4",
- "LeftArrowRightArrow;": "\u21c6",
- "LeftCeiling;": "\u2308",
- "LeftDoubleBracket;": "\u27e6",
- "LeftDownTeeVector;": "\u2961",
- "LeftDownVector;": "\u21c3",
- "LeftDownVectorBar;": "\u2959",
- "LeftFloor;": "\u230a",
- "LeftRightArrow;": "\u2194",
- "LeftRightVector;": "\u294e",
- "LeftTee;": "\u22a3",
- "LeftTeeArrow;": "\u21a4",
- "LeftTeeVector;": "\u295a",
- "LeftTriangle;": "\u22b2",
- "LeftTriangleBar;": "\u29cf",
- "LeftTriangleEqual;": "\u22b4",
- "LeftUpDownVector;": "\u2951",
- "LeftUpTeeVector;": "\u2960",
- "LeftUpVector;": "\u21bf",
- "LeftUpVectorBar;": "\u2958",
- "LeftVector;": "\u21bc",
- "LeftVectorBar;": "\u2952",
- "Leftarrow;": "\u21d0",
- "Leftrightarrow;": "\u21d4",
- "LessEqualGreater;": "\u22da",
- "LessFullEqual;": "\u2266",
- "LessGreater;": "\u2276",
- "LessLess;": "\u2aa1",
- "LessSlantEqual;": "\u2a7d",
- "LessTilde;": "\u2272",
- "Lfr;": "\U0001d50f",
- "Ll;": "\u22d8",
- "Lleftarrow;": "\u21da",
- "Lmidot;": "\u013f",
- "LongLeftArrow;": "\u27f5",
- "LongLeftRightArrow;": "\u27f7",
- "LongRightArrow;": "\u27f6",
- "Longleftarrow;": "\u27f8",
- "Longleftrightarrow;": "\u27fa",
- "Longrightarrow;": "\u27f9",
- "Lopf;": "\U0001d543",
- "LowerLeftArrow;": "\u2199",
- "LowerRightArrow;": "\u2198",
- "Lscr;": "\u2112",
- "Lsh;": "\u21b0",
- "Lstrok;": "\u0141",
- "Lt;": "\u226a",
- "Map;": "\u2905",
- "Mcy;": "\u041c",
- "MediumSpace;": "\u205f",
- "Mellintrf;": "\u2133",
- "Mfr;": "\U0001d510",
- "MinusPlus;": "\u2213",
- "Mopf;": "\U0001d544",
- "Mscr;": "\u2133",
- "Mu;": "\u039c",
- "NJcy;": "\u040a",
- "Nacute;": "\u0143",
- "Ncaron;": "\u0147",
- "Ncedil;": "\u0145",
- "Ncy;": "\u041d",
- "NegativeMediumSpace;": "\u200b",
- "NegativeThickSpace;": "\u200b",
- "NegativeThinSpace;": "\u200b",
- "NegativeVeryThinSpace;": "\u200b",
- "NestedGreaterGreater;": "\u226b",
- "NestedLessLess;": "\u226a",
- "NewLine;": "\n",
- "Nfr;": "\U0001d511",
- "NoBreak;": "\u2060",
- "NonBreakingSpace;": "\xa0",
- "Nopf;": "\u2115",
- "Not;": "\u2aec",
- "NotCongruent;": "\u2262",
- "NotCupCap;": "\u226d",
- "NotDoubleVerticalBar;": "\u2226",
- "NotElement;": "\u2209",
- "NotEqual;": "\u2260",
- "NotEqualTilde;": "\u2242\u0338",
- "NotExists;": "\u2204",
- "NotGreater;": "\u226f",
- "NotGreaterEqual;": "\u2271",
- "NotGreaterFullEqual;": "\u2267\u0338",
- "NotGreaterGreater;": "\u226b\u0338",
- "NotGreaterLess;": "\u2279",
- "NotGreaterSlantEqual;": "\u2a7e\u0338",
- "NotGreaterTilde;": "\u2275",
- "NotHumpDownHump;": "\u224e\u0338",
- "NotHumpEqual;": "\u224f\u0338",
- "NotLeftTriangle;": "\u22ea",
- "NotLeftTriangleBar;": "\u29cf\u0338",
- "NotLeftTriangleEqual;": "\u22ec",
- "NotLess;": "\u226e",
- "NotLessEqual;": "\u2270",
- "NotLessGreater;": "\u2278",
- "NotLessLess;": "\u226a\u0338",
- "NotLessSlantEqual;": "\u2a7d\u0338",
- "NotLessTilde;": "\u2274",
- "NotNestedGreaterGreater;": "\u2aa2\u0338",
- "NotNestedLessLess;": "\u2aa1\u0338",
- "NotPrecedes;": "\u2280",
- "NotPrecedesEqual;": "\u2aaf\u0338",
- "NotPrecedesSlantEqual;": "\u22e0",
- "NotReverseElement;": "\u220c",
- "NotRightTriangle;": "\u22eb",
- "NotRightTriangleBar;": "\u29d0\u0338",
- "NotRightTriangleEqual;": "\u22ed",
- "NotSquareSubset;": "\u228f\u0338",
- "NotSquareSubsetEqual;": "\u22e2",
- "NotSquareSuperset;": "\u2290\u0338",
- "NotSquareSupersetEqual;": "\u22e3",
- "NotSubset;": "\u2282\u20d2",
- "NotSubsetEqual;": "\u2288",
- "NotSucceeds;": "\u2281",
- "NotSucceedsEqual;": "\u2ab0\u0338",
- "NotSucceedsSlantEqual;": "\u22e1",
- "NotSucceedsTilde;": "\u227f\u0338",
- "NotSuperset;": "\u2283\u20d2",
- "NotSupersetEqual;": "\u2289",
- "NotTilde;": "\u2241",
- "NotTildeEqual;": "\u2244",
- "NotTildeFullEqual;": "\u2247",
- "NotTildeTilde;": "\u2249",
- "NotVerticalBar;": "\u2224",
- "Nscr;": "\U0001d4a9",
- "Ntilde": "\xd1",
- "Ntilde;": "\xd1",
- "Nu;": "\u039d",
- "OElig;": "\u0152",
- "Oacute": "\xd3",
- "Oacute;": "\xd3",
- "Ocirc": "\xd4",
- "Ocirc;": "\xd4",
- "Ocy;": "\u041e",
- "Odblac;": "\u0150",
- "Ofr;": "\U0001d512",
- "Ograve": "\xd2",
- "Ograve;": "\xd2",
- "Omacr;": "\u014c",
- "Omega;": "\u03a9",
- "Omicron;": "\u039f",
- "Oopf;": "\U0001d546",
- "OpenCurlyDoubleQuote;": "\u201c",
- "OpenCurlyQuote;": "\u2018",
- "Or;": "\u2a54",
- "Oscr;": "\U0001d4aa",
- "Oslash": "\xd8",
- "Oslash;": "\xd8",
- "Otilde": "\xd5",
- "Otilde;": "\xd5",
- "Otimes;": "\u2a37",
- "Ouml": "\xd6",
- "Ouml;": "\xd6",
- "OverBar;": "\u203e",
- "OverBrace;": "\u23de",
- "OverBracket;": "\u23b4",
- "OverParenthesis;": "\u23dc",
- "PartialD;": "\u2202",
- "Pcy;": "\u041f",
- "Pfr;": "\U0001d513",
- "Phi;": "\u03a6",
- "Pi;": "\u03a0",
- "PlusMinus;": "\xb1",
- "Poincareplane;": "\u210c",
- "Popf;": "\u2119",
- "Pr;": "\u2abb",
- "Precedes;": "\u227a",
- "PrecedesEqual;": "\u2aaf",
- "PrecedesSlantEqual;": "\u227c",
- "PrecedesTilde;": "\u227e",
- "Prime;": "\u2033",
- "Product;": "\u220f",
- "Proportion;": "\u2237",
- "Proportional;": "\u221d",
- "Pscr;": "\U0001d4ab",
- "Psi;": "\u03a8",
- "QUOT": "\"",
- "QUOT;": "\"",
- "Qfr;": "\U0001d514",
- "Qopf;": "\u211a",
- "Qscr;": "\U0001d4ac",
- "RBarr;": "\u2910",
- "REG": "\xae",
- "REG;": "\xae",
- "Racute;": "\u0154",
- "Rang;": "\u27eb",
- "Rarr;": "\u21a0",
- "Rarrtl;": "\u2916",
- "Rcaron;": "\u0158",
- "Rcedil;": "\u0156",
- "Rcy;": "\u0420",
- "Re;": "\u211c",
- "ReverseElement;": "\u220b",
- "ReverseEquilibrium;": "\u21cb",
- "ReverseUpEquilibrium;": "\u296f",
- "Rfr;": "\u211c",
- "Rho;": "\u03a1",
- "RightAngleBracket;": "\u27e9",
- "RightArrow;": "\u2192",
- "RightArrowBar;": "\u21e5",
- "RightArrowLeftArrow;": "\u21c4",
- "RightCeiling;": "\u2309",
- "RightDoubleBracket;": "\u27e7",
- "RightDownTeeVector;": "\u295d",
- "RightDownVector;": "\u21c2",
- "RightDownVectorBar;": "\u2955",
- "RightFloor;": "\u230b",
- "RightTee;": "\u22a2",
- "RightTeeArrow;": "\u21a6",
- "RightTeeVector;": "\u295b",
- "RightTriangle;": "\u22b3",
- "RightTriangleBar;": "\u29d0",
- "RightTriangleEqual;": "\u22b5",
- "RightUpDownVector;": "\u294f",
- "RightUpTeeVector;": "\u295c",
- "RightUpVector;": "\u21be",
- "RightUpVectorBar;": "\u2954",
- "RightVector;": "\u21c0",
- "RightVectorBar;": "\u2953",
- "Rightarrow;": "\u21d2",
- "Ropf;": "\u211d",
- "RoundImplies;": "\u2970",
- "Rrightarrow;": "\u21db",
- "Rscr;": "\u211b",
- "Rsh;": "\u21b1",
- "RuleDelayed;": "\u29f4",
- "SHCHcy;": "\u0429",
- "SHcy;": "\u0428",
- "SOFTcy;": "\u042c",
- "Sacute;": "\u015a",
- "Sc;": "\u2abc",
- "Scaron;": "\u0160",
- "Scedil;": "\u015e",
- "Scirc;": "\u015c",
- "Scy;": "\u0421",
- "Sfr;": "\U0001d516",
- "ShortDownArrow;": "\u2193",
- "ShortLeftArrow;": "\u2190",
- "ShortRightArrow;": "\u2192",
- "ShortUpArrow;": "\u2191",
- "Sigma;": "\u03a3",
- "SmallCircle;": "\u2218",
- "Sopf;": "\U0001d54a",
- "Sqrt;": "\u221a",
- "Square;": "\u25a1",
- "SquareIntersection;": "\u2293",
- "SquareSubset;": "\u228f",
- "SquareSubsetEqual;": "\u2291",
- "SquareSuperset;": "\u2290",
- "SquareSupersetEqual;": "\u2292",
- "SquareUnion;": "\u2294",
- "Sscr;": "\U0001d4ae",
- "Star;": "\u22c6",
- "Sub;": "\u22d0",
- "Subset;": "\u22d0",
- "SubsetEqual;": "\u2286",
- "Succeeds;": "\u227b",
- "SucceedsEqual;": "\u2ab0",
- "SucceedsSlantEqual;": "\u227d",
- "SucceedsTilde;": "\u227f",
- "SuchThat;": "\u220b",
- "Sum;": "\u2211",
- "Sup;": "\u22d1",
- "Superset;": "\u2283",
- "SupersetEqual;": "\u2287",
- "Supset;": "\u22d1",
- "THORN": "\xde",
- "THORN;": "\xde",
- "TRADE;": "\u2122",
- "TSHcy;": "\u040b",
- "TScy;": "\u0426",
- "Tab;": "\t",
- "Tau;": "\u03a4",
- "Tcaron;": "\u0164",
- "Tcedil;": "\u0162",
- "Tcy;": "\u0422",
- "Tfr;": "\U0001d517",
- "Therefore;": "\u2234",
- "Theta;": "\u0398",
- "ThickSpace;": "\u205f\u200a",
- "ThinSpace;": "\u2009",
- "Tilde;": "\u223c",
- "TildeEqual;": "\u2243",
- "TildeFullEqual;": "\u2245",
- "TildeTilde;": "\u2248",
- "Topf;": "\U0001d54b",
- "TripleDot;": "\u20db",
- "Tscr;": "\U0001d4af",
- "Tstrok;": "\u0166",
- "Uacute": "\xda",
- "Uacute;": "\xda",
- "Uarr;": "\u219f",
- "Uarrocir;": "\u2949",
- "Ubrcy;": "\u040e",
- "Ubreve;": "\u016c",
- "Ucirc": "\xdb",
- "Ucirc;": "\xdb",
- "Ucy;": "\u0423",
- "Udblac;": "\u0170",
- "Ufr;": "\U0001d518",
- "Ugrave": "\xd9",
- "Ugrave;": "\xd9",
- "Umacr;": "\u016a",
- "UnderBar;": "_",
- "UnderBrace;": "\u23df",
- "UnderBracket;": "\u23b5",
- "UnderParenthesis;": "\u23dd",
- "Union;": "\u22c3",
- "UnionPlus;": "\u228e",
- "Uogon;": "\u0172",
- "Uopf;": "\U0001d54c",
- "UpArrow;": "\u2191",
- "UpArrowBar;": "\u2912",
- "UpArrowDownArrow;": "\u21c5",
- "UpDownArrow;": "\u2195",
- "UpEquilibrium;": "\u296e",
- "UpTee;": "\u22a5",
- "UpTeeArrow;": "\u21a5",
- "Uparrow;": "\u21d1",
- "Updownarrow;": "\u21d5",
- "UpperLeftArrow;": "\u2196",
- "UpperRightArrow;": "\u2197",
- "Upsi;": "\u03d2",
- "Upsilon;": "\u03a5",
- "Uring;": "\u016e",
- "Uscr;": "\U0001d4b0",
- "Utilde;": "\u0168",
- "Uuml": "\xdc",
- "Uuml;": "\xdc",
- "VDash;": "\u22ab",
- "Vbar;": "\u2aeb",
- "Vcy;": "\u0412",
- "Vdash;": "\u22a9",
- "Vdashl;": "\u2ae6",
- "Vee;": "\u22c1",
- "Verbar;": "\u2016",
- "Vert;": "\u2016",
- "VerticalBar;": "\u2223",
- "VerticalLine;": "|",
- "VerticalSeparator;": "\u2758",
- "VerticalTilde;": "\u2240",
- "VeryThinSpace;": "\u200a",
- "Vfr;": "\U0001d519",
- "Vopf;": "\U0001d54d",
- "Vscr;": "\U0001d4b1",
- "Vvdash;": "\u22aa",
- "Wcirc;": "\u0174",
- "Wedge;": "\u22c0",
- "Wfr;": "\U0001d51a",
- "Wopf;": "\U0001d54e",
- "Wscr;": "\U0001d4b2",
- "Xfr;": "\U0001d51b",
- "Xi;": "\u039e",
- "Xopf;": "\U0001d54f",
- "Xscr;": "\U0001d4b3",
- "YAcy;": "\u042f",
- "YIcy;": "\u0407",
- "YUcy;": "\u042e",
- "Yacute": "\xdd",
- "Yacute;": "\xdd",
- "Ycirc;": "\u0176",
- "Ycy;": "\u042b",
- "Yfr;": "\U0001d51c",
- "Yopf;": "\U0001d550",
- "Yscr;": "\U0001d4b4",
- "Yuml;": "\u0178",
- "ZHcy;": "\u0416",
- "Zacute;": "\u0179",
- "Zcaron;": "\u017d",
- "Zcy;": "\u0417",
- "Zdot;": "\u017b",
- "ZeroWidthSpace;": "\u200b",
- "Zeta;": "\u0396",
- "Zfr;": "\u2128",
- "Zopf;": "\u2124",
- "Zscr;": "\U0001d4b5",
- "aacute": "\xe1",
- "aacute;": "\xe1",
- "abreve;": "\u0103",
- "ac;": "\u223e",
- "acE;": "\u223e\u0333",
- "acd;": "\u223f",
- "acirc": "\xe2",
- "acirc;": "\xe2",
- "acute": "\xb4",
- "acute;": "\xb4",
- "acy;": "\u0430",
- "aelig": "\xe6",
- "aelig;": "\xe6",
- "af;": "\u2061",
- "afr;": "\U0001d51e",
- "agrave": "\xe0",
- "agrave;": "\xe0",
- "alefsym;": "\u2135",
- "aleph;": "\u2135",
- "alpha;": "\u03b1",
- "amacr;": "\u0101",
- "amalg;": "\u2a3f",
- "amp": "&",
- "amp;": "&",
- "and;": "\u2227",
- "andand;": "\u2a55",
- "andd;": "\u2a5c",
- "andslope;": "\u2a58",
- "andv;": "\u2a5a",
- "ang;": "\u2220",
- "ange;": "\u29a4",
- "angle;": "\u2220",
- "angmsd;": "\u2221",
- "angmsdaa;": "\u29a8",
- "angmsdab;": "\u29a9",
- "angmsdac;": "\u29aa",
- "angmsdad;": "\u29ab",
- "angmsdae;": "\u29ac",
- "angmsdaf;": "\u29ad",
- "angmsdag;": "\u29ae",
- "angmsdah;": "\u29af",
- "angrt;": "\u221f",
- "angrtvb;": "\u22be",
- "angrtvbd;": "\u299d",
- "angsph;": "\u2222",
- "angst;": "\xc5",
- "angzarr;": "\u237c",
- "aogon;": "\u0105",
- "aopf;": "\U0001d552",
- "ap;": "\u2248",
- "apE;": "\u2a70",
- "apacir;": "\u2a6f",
- "ape;": "\u224a",
- "apid;": "\u224b",
- "apos;": "'",
- "approx;": "\u2248",
- "approxeq;": "\u224a",
- "aring": "\xe5",
- "aring;": "\xe5",
- "ascr;": "\U0001d4b6",
- "ast;": "*",
- "asymp;": "\u2248",
- "asympeq;": "\u224d",
- "atilde": "\xe3",
- "atilde;": "\xe3",
- "auml": "\xe4",
- "auml;": "\xe4",
- "awconint;": "\u2233",
- "awint;": "\u2a11",
- "bNot;": "\u2aed",
- "backcong;": "\u224c",
- "backepsilon;": "\u03f6",
- "backprime;": "\u2035",
- "backsim;": "\u223d",
- "backsimeq;": "\u22cd",
- "barvee;": "\u22bd",
- "barwed;": "\u2305",
- "barwedge;": "\u2305",
- "bbrk;": "\u23b5",
- "bbrktbrk;": "\u23b6",
- "bcong;": "\u224c",
- "bcy;": "\u0431",
- "bdquo;": "\u201e",
- "becaus;": "\u2235",
- "because;": "\u2235",
- "bemptyv;": "\u29b0",
- "bepsi;": "\u03f6",
- "bernou;": "\u212c",
- "beta;": "\u03b2",
- "beth;": "\u2136",
- "between;": "\u226c",
- "bfr;": "\U0001d51f",
- "bigcap;": "\u22c2",
- "bigcirc;": "\u25ef",
- "bigcup;": "\u22c3",
- "bigodot;": "\u2a00",
- "bigoplus;": "\u2a01",
- "bigotimes;": "\u2a02",
- "bigsqcup;": "\u2a06",
- "bigstar;": "\u2605",
- "bigtriangledown;": "\u25bd",
- "bigtriangleup;": "\u25b3",
- "biguplus;": "\u2a04",
- "bigvee;": "\u22c1",
- "bigwedge;": "\u22c0",
- "bkarow;": "\u290d",
- "blacklozenge;": "\u29eb",
- "blacksquare;": "\u25aa",
- "blacktriangle;": "\u25b4",
- "blacktriangledown;": "\u25be",
- "blacktriangleleft;": "\u25c2",
- "blacktriangleright;": "\u25b8",
- "blank;": "\u2423",
- "blk12;": "\u2592",
- "blk14;": "\u2591",
- "blk34;": "\u2593",
- "block;": "\u2588",
- "bne;": "=\u20e5",
- "bnequiv;": "\u2261\u20e5",
- "bnot;": "\u2310",
- "bopf;": "\U0001d553",
- "bot;": "\u22a5",
- "bottom;": "\u22a5",
- "bowtie;": "\u22c8",
- "boxDL;": "\u2557",
- "boxDR;": "\u2554",
- "boxDl;": "\u2556",
- "boxDr;": "\u2553",
- "boxH;": "\u2550",
- "boxHD;": "\u2566",
- "boxHU;": "\u2569",
- "boxHd;": "\u2564",
- "boxHu;": "\u2567",
- "boxUL;": "\u255d",
- "boxUR;": "\u255a",
- "boxUl;": "\u255c",
- "boxUr;": "\u2559",
- "boxV;": "\u2551",
- "boxVH;": "\u256c",
- "boxVL;": "\u2563",
- "boxVR;": "\u2560",
- "boxVh;": "\u256b",
- "boxVl;": "\u2562",
- "boxVr;": "\u255f",
- "boxbox;": "\u29c9",
- "boxdL;": "\u2555",
- "boxdR;": "\u2552",
- "boxdl;": "\u2510",
- "boxdr;": "\u250c",
- "boxh;": "\u2500",
- "boxhD;": "\u2565",
- "boxhU;": "\u2568",
- "boxhd;": "\u252c",
- "boxhu;": "\u2534",
- "boxminus;": "\u229f",
- "boxplus;": "\u229e",
- "boxtimes;": "\u22a0",
- "boxuL;": "\u255b",
- "boxuR;": "\u2558",
- "boxul;": "\u2518",
- "boxur;": "\u2514",
- "boxv;": "\u2502",
- "boxvH;": "\u256a",
- "boxvL;": "\u2561",
- "boxvR;": "\u255e",
- "boxvh;": "\u253c",
- "boxvl;": "\u2524",
- "boxvr;": "\u251c",
- "bprime;": "\u2035",
- "breve;": "\u02d8",
- "brvbar": "\xa6",
- "brvbar;": "\xa6",
- "bscr;": "\U0001d4b7",
- "bsemi;": "\u204f",
- "bsim;": "\u223d",
- "bsime;": "\u22cd",
- "bsol;": "\\",
- "bsolb;": "\u29c5",
- "bsolhsub;": "\u27c8",
- "bull;": "\u2022",
- "bullet;": "\u2022",
- "bump;": "\u224e",
- "bumpE;": "\u2aae",
- "bumpe;": "\u224f",
- "bumpeq;": "\u224f",
- "cacute;": "\u0107",
- "cap;": "\u2229",
- "capand;": "\u2a44",
- "capbrcup;": "\u2a49",
- "capcap;": "\u2a4b",
- "capcup;": "\u2a47",
- "capdot;": "\u2a40",
- "caps;": "\u2229\ufe00",
- "caret;": "\u2041",
- "caron;": "\u02c7",
- "ccaps;": "\u2a4d",
- "ccaron;": "\u010d",
- "ccedil": "\xe7",
- "ccedil;": "\xe7",
- "ccirc;": "\u0109",
- "ccups;": "\u2a4c",
- "ccupssm;": "\u2a50",
- "cdot;": "\u010b",
- "cedil": "\xb8",
- "cedil;": "\xb8",
- "cemptyv;": "\u29b2",
- "cent": "\xa2",
- "cent;": "\xa2",
- "centerdot;": "\xb7",
- "cfr;": "\U0001d520",
- "chcy;": "\u0447",
- "check;": "\u2713",
- "checkmark;": "\u2713",
- "chi;": "\u03c7",
- "cir;": "\u25cb",
- "cirE;": "\u29c3",
- "circ;": "\u02c6",
- "circeq;": "\u2257",
- "circlearrowleft;": "\u21ba",
- "circlearrowright;": "\u21bb",
- "circledR;": "\xae",
- "circledS;": "\u24c8",
- "circledast;": "\u229b",
- "circledcirc;": "\u229a",
- "circleddash;": "\u229d",
- "cire;": "\u2257",
- "cirfnint;": "\u2a10",
- "cirmid;": "\u2aef",
- "cirscir;": "\u29c2",
- "clubs;": "\u2663",
- "clubsuit;": "\u2663",
- "colon;": ":",
- "colone;": "\u2254",
- "coloneq;": "\u2254",
- "comma;": ",",
- "commat;": "@",
- "comp;": "\u2201",
- "compfn;": "\u2218",
- "complement;": "\u2201",
- "complexes;": "\u2102",
- "cong;": "\u2245",
- "congdot;": "\u2a6d",
- "conint;": "\u222e",
- "copf;": "\U0001d554",
- "coprod;": "\u2210",
- "copy": "\xa9",
- "copy;": "\xa9",
- "copysr;": "\u2117",
- "crarr;": "\u21b5",
- "cross;": "\u2717",
- "cscr;": "\U0001d4b8",
- "csub;": "\u2acf",
- "csube;": "\u2ad1",
- "csup;": "\u2ad0",
- "csupe;": "\u2ad2",
- "ctdot;": "\u22ef",
- "cudarrl;": "\u2938",
- "cudarrr;": "\u2935",
- "cuepr;": "\u22de",
- "cuesc;": "\u22df",
- "cularr;": "\u21b6",
- "cularrp;": "\u293d",
- "cup;": "\u222a",
- "cupbrcap;": "\u2a48",
- "cupcap;": "\u2a46",
- "cupcup;": "\u2a4a",
- "cupdot;": "\u228d",
- "cupor;": "\u2a45",
- "cups;": "\u222a\ufe00",
- "curarr;": "\u21b7",
- "curarrm;": "\u293c",
- "curlyeqprec;": "\u22de",
- "curlyeqsucc;": "\u22df",
- "curlyvee;": "\u22ce",
- "curlywedge;": "\u22cf",
- "curren": "\xa4",
- "curren;": "\xa4",
- "curvearrowleft;": "\u21b6",
- "curvearrowright;": "\u21b7",
- "cuvee;": "\u22ce",
- "cuwed;": "\u22cf",
- "cwconint;": "\u2232",
- "cwint;": "\u2231",
- "cylcty;": "\u232d",
- "dArr;": "\u21d3",
- "dHar;": "\u2965",
- "dagger;": "\u2020",
- "daleth;": "\u2138",
- "darr;": "\u2193",
- "dash;": "\u2010",
- "dashv;": "\u22a3",
- "dbkarow;": "\u290f",
- "dblac;": "\u02dd",
- "dcaron;": "\u010f",
- "dcy;": "\u0434",
- "dd;": "\u2146",
- "ddagger;": "\u2021",
- "ddarr;": "\u21ca",
- "ddotseq;": "\u2a77",
- "deg": "\xb0",
- "deg;": "\xb0",
- "delta;": "\u03b4",
- "demptyv;": "\u29b1",
- "dfisht;": "\u297f",
- "dfr;": "\U0001d521",
- "dharl;": "\u21c3",
- "dharr;": "\u21c2",
- "diam;": "\u22c4",
- "diamond;": "\u22c4",
- "diamondsuit;": "\u2666",
- "diams;": "\u2666",
- "die;": "\xa8",
- "digamma;": "\u03dd",
- "disin;": "\u22f2",
- "div;": "\xf7",
- "divide": "\xf7",
- "divide;": "\xf7",
- "divideontimes;": "\u22c7",
- "divonx;": "\u22c7",
- "djcy;": "\u0452",
- "dlcorn;": "\u231e",
- "dlcrop;": "\u230d",
- "dollar;": "$",
- "dopf;": "\U0001d555",
- "dot;": "\u02d9",
- "doteq;": "\u2250",
- "doteqdot;": "\u2251",
- "dotminus;": "\u2238",
- "dotplus;": "\u2214",
- "dotsquare;": "\u22a1",
- "doublebarwedge;": "\u2306",
- "downarrow;": "\u2193",
- "downdownarrows;": "\u21ca",
- "downharpoonleft;": "\u21c3",
- "downharpoonright;": "\u21c2",
- "drbkarow;": "\u2910",
- "drcorn;": "\u231f",
- "drcrop;": "\u230c",
- "dscr;": "\U0001d4b9",
- "dscy;": "\u0455",
- "dsol;": "\u29f6",
- "dstrok;": "\u0111",
- "dtdot;": "\u22f1",
- "dtri;": "\u25bf",
- "dtrif;": "\u25be",
- "duarr;": "\u21f5",
- "duhar;": "\u296f",
- "dwangle;": "\u29a6",
- "dzcy;": "\u045f",
- "dzigrarr;": "\u27ff",
- "eDDot;": "\u2a77",
- "eDot;": "\u2251",
- "eacute": "\xe9",
- "eacute;": "\xe9",
- "easter;": "\u2a6e",
- "ecaron;": "\u011b",
- "ecir;": "\u2256",
- "ecirc": "\xea",
- "ecirc;": "\xea",
- "ecolon;": "\u2255",
- "ecy;": "\u044d",
- "edot;": "\u0117",
- "ee;": "\u2147",
- "efDot;": "\u2252",
- "efr;": "\U0001d522",
- "eg;": "\u2a9a",
- "egrave": "\xe8",
- "egrave;": "\xe8",
- "egs;": "\u2a96",
- "egsdot;": "\u2a98",
- "el;": "\u2a99",
- "elinters;": "\u23e7",
- "ell;": "\u2113",
- "els;": "\u2a95",
- "elsdot;": "\u2a97",
- "emacr;": "\u0113",
- "empty;": "\u2205",
- "emptyset;": "\u2205",
- "emptyv;": "\u2205",
- "emsp13;": "\u2004",
- "emsp14;": "\u2005",
- "emsp;": "\u2003",
- "eng;": "\u014b",
- "ensp;": "\u2002",
- "eogon;": "\u0119",
- "eopf;": "\U0001d556",
- "epar;": "\u22d5",
- "eparsl;": "\u29e3",
- "eplus;": "\u2a71",
- "epsi;": "\u03b5",
- "epsilon;": "\u03b5",
- "epsiv;": "\u03f5",
- "eqcirc;": "\u2256",
- "eqcolon;": "\u2255",
- "eqsim;": "\u2242",
- "eqslantgtr;": "\u2a96",
- "eqslantless;": "\u2a95",
- "equals;": "=",
- "equest;": "\u225f",
- "equiv;": "\u2261",
- "equivDD;": "\u2a78",
- "eqvparsl;": "\u29e5",
- "erDot;": "\u2253",
- "erarr;": "\u2971",
- "escr;": "\u212f",
- "esdot;": "\u2250",
- "esim;": "\u2242",
- "eta;": "\u03b7",
- "eth": "\xf0",
- "eth;": "\xf0",
- "euml": "\xeb",
- "euml;": "\xeb",
- "euro;": "\u20ac",
- "excl;": "!",
- "exist;": "\u2203",
- "expectation;": "\u2130",
- "exponentiale;": "\u2147",
- "fallingdotseq;": "\u2252",
- "fcy;": "\u0444",
- "female;": "\u2640",
- "ffilig;": "\ufb03",
- "fflig;": "\ufb00",
- "ffllig;": "\ufb04",
- "ffr;": "\U0001d523",
- "filig;": "\ufb01",
- "fjlig;": "fj",
- "flat;": "\u266d",
- "fllig;": "\ufb02",
- "fltns;": "\u25b1",
- "fnof;": "\u0192",
- "fopf;": "\U0001d557",
- "forall;": "\u2200",
- "fork;": "\u22d4",
- "forkv;": "\u2ad9",
- "fpartint;": "\u2a0d",
- "frac12": "\xbd",
- "frac12;": "\xbd",
- "frac13;": "\u2153",
- "frac14": "\xbc",
- "frac14;": "\xbc",
- "frac15;": "\u2155",
- "frac16;": "\u2159",
- "frac18;": "\u215b",
- "frac23;": "\u2154",
- "frac25;": "\u2156",
- "frac34": "\xbe",
- "frac34;": "\xbe",
- "frac35;": "\u2157",
- "frac38;": "\u215c",
- "frac45;": "\u2158",
- "frac56;": "\u215a",
- "frac58;": "\u215d",
- "frac78;": "\u215e",
- "frasl;": "\u2044",
- "frown;": "\u2322",
- "fscr;": "\U0001d4bb",
- "gE;": "\u2267",
- "gEl;": "\u2a8c",
- "gacute;": "\u01f5",
- "gamma;": "\u03b3",
- "gammad;": "\u03dd",
- "gap;": "\u2a86",
- "gbreve;": "\u011f",
- "gcirc;": "\u011d",
- "gcy;": "\u0433",
- "gdot;": "\u0121",
- "ge;": "\u2265",
- "gel;": "\u22db",
- "geq;": "\u2265",
- "geqq;": "\u2267",
- "geqslant;": "\u2a7e",
- "ges;": "\u2a7e",
- "gescc;": "\u2aa9",
- "gesdot;": "\u2a80",
- "gesdoto;": "\u2a82",
- "gesdotol;": "\u2a84",
- "gesl;": "\u22db\ufe00",
- "gesles;": "\u2a94",
- "gfr;": "\U0001d524",
- "gg;": "\u226b",
- "ggg;": "\u22d9",
- "gimel;": "\u2137",
- "gjcy;": "\u0453",
- "gl;": "\u2277",
- "glE;": "\u2a92",
- "gla;": "\u2aa5",
- "glj;": "\u2aa4",
- "gnE;": "\u2269",
- "gnap;": "\u2a8a",
- "gnapprox;": "\u2a8a",
- "gne;": "\u2a88",
- "gneq;": "\u2a88",
- "gneqq;": "\u2269",
- "gnsim;": "\u22e7",
- "gopf;": "\U0001d558",
- "grave;": "`",
- "gscr;": "\u210a",
- "gsim;": "\u2273",
- "gsime;": "\u2a8e",
- "gsiml;": "\u2a90",
- "gt": ">",
- "gt;": ">",
- "gtcc;": "\u2aa7",
- "gtcir;": "\u2a7a",
- "gtdot;": "\u22d7",
- "gtlPar;": "\u2995",
- "gtquest;": "\u2a7c",
- "gtrapprox;": "\u2a86",
- "gtrarr;": "\u2978",
- "gtrdot;": "\u22d7",
- "gtreqless;": "\u22db",
- "gtreqqless;": "\u2a8c",
- "gtrless;": "\u2277",
- "gtrsim;": "\u2273",
- "gvertneqq;": "\u2269\ufe00",
- "gvnE;": "\u2269\ufe00",
- "hArr;": "\u21d4",
- "hairsp;": "\u200a",
- "half;": "\xbd",
- "hamilt;": "\u210b",
- "hardcy;": "\u044a",
- "harr;": "\u2194",
- "harrcir;": "\u2948",
- "harrw;": "\u21ad",
- "hbar;": "\u210f",
- "hcirc;": "\u0125",
- "hearts;": "\u2665",
- "heartsuit;": "\u2665",
- "hellip;": "\u2026",
- "hercon;": "\u22b9",
- "hfr;": "\U0001d525",
- "hksearow;": "\u2925",
- "hkswarow;": "\u2926",
- "hoarr;": "\u21ff",
- "homtht;": "\u223b",
- "hookleftarrow;": "\u21a9",
- "hookrightarrow;": "\u21aa",
- "hopf;": "\U0001d559",
- "horbar;": "\u2015",
- "hscr;": "\U0001d4bd",
- "hslash;": "\u210f",
- "hstrok;": "\u0127",
- "hybull;": "\u2043",
- "hyphen;": "\u2010",
- "iacute": "\xed",
- "iacute;": "\xed",
- "ic;": "\u2063",
- "icirc": "\xee",
- "icirc;": "\xee",
- "icy;": "\u0438",
- "iecy;": "\u0435",
- "iexcl": "\xa1",
- "iexcl;": "\xa1",
- "iff;": "\u21d4",
- "ifr;": "\U0001d526",
- "igrave": "\xec",
- "igrave;": "\xec",
- "ii;": "\u2148",
- "iiiint;": "\u2a0c",
- "iiint;": "\u222d",
- "iinfin;": "\u29dc",
- "iiota;": "\u2129",
- "ijlig;": "\u0133",
- "imacr;": "\u012b",
- "image;": "\u2111",
- "imagline;": "\u2110",
- "imagpart;": "\u2111",
- "imath;": "\u0131",
- "imof;": "\u22b7",
- "imped;": "\u01b5",
- "in;": "\u2208",
- "incare;": "\u2105",
- "infin;": "\u221e",
- "infintie;": "\u29dd",
- "inodot;": "\u0131",
- "int;": "\u222b",
- "intcal;": "\u22ba",
- "integers;": "\u2124",
- "intercal;": "\u22ba",
- "intlarhk;": "\u2a17",
- "intprod;": "\u2a3c",
- "iocy;": "\u0451",
- "iogon;": "\u012f",
- "iopf;": "\U0001d55a",
- "iota;": "\u03b9",
- "iprod;": "\u2a3c",
- "iquest": "\xbf",
- "iquest;": "\xbf",
- "iscr;": "\U0001d4be",
- "isin;": "\u2208",
- "isinE;": "\u22f9",
- "isindot;": "\u22f5",
- "isins;": "\u22f4",
- "isinsv;": "\u22f3",
- "isinv;": "\u2208",
- "it;": "\u2062",
- "itilde;": "\u0129",
- "iukcy;": "\u0456",
- "iuml": "\xef",
- "iuml;": "\xef",
- "jcirc;": "\u0135",
- "jcy;": "\u0439",
- "jfr;": "\U0001d527",
- "jmath;": "\u0237",
- "jopf;": "\U0001d55b",
- "jscr;": "\U0001d4bf",
- "jsercy;": "\u0458",
- "jukcy;": "\u0454",
- "kappa;": "\u03ba",
- "kappav;": "\u03f0",
- "kcedil;": "\u0137",
- "kcy;": "\u043a",
- "kfr;": "\U0001d528",
- "kgreen;": "\u0138",
- "khcy;": "\u0445",
- "kjcy;": "\u045c",
- "kopf;": "\U0001d55c",
- "kscr;": "\U0001d4c0",
- "lAarr;": "\u21da",
- "lArr;": "\u21d0",
- "lAtail;": "\u291b",
- "lBarr;": "\u290e",
- "lE;": "\u2266",
- "lEg;": "\u2a8b",
- "lHar;": "\u2962",
- "lacute;": "\u013a",
- "laemptyv;": "\u29b4",
- "lagran;": "\u2112",
- "lambda;": "\u03bb",
- "lang;": "\u27e8",
- "langd;": "\u2991",
- "langle;": "\u27e8",
- "lap;": "\u2a85",
- "laquo": "\xab",
- "laquo;": "\xab",
- "larr;": "\u2190",
- "larrb;": "\u21e4",
- "larrbfs;": "\u291f",
- "larrfs;": "\u291d",
- "larrhk;": "\u21a9",
- "larrlp;": "\u21ab",
- "larrpl;": "\u2939",
- "larrsim;": "\u2973",
- "larrtl;": "\u21a2",
- "lat;": "\u2aab",
- "latail;": "\u2919",
- "late;": "\u2aad",
- "lates;": "\u2aad\ufe00",
- "lbarr;": "\u290c",
- "lbbrk;": "\u2772",
- "lbrace;": "{",
- "lbrack;": "[",
- "lbrke;": "\u298b",
- "lbrksld;": "\u298f",
- "lbrkslu;": "\u298d",
- "lcaron;": "\u013e",
- "lcedil;": "\u013c",
- "lceil;": "\u2308",
- "lcub;": "{",
- "lcy;": "\u043b",
- "ldca;": "\u2936",
- "ldquo;": "\u201c",
- "ldquor;": "\u201e",
- "ldrdhar;": "\u2967",
- "ldrushar;": "\u294b",
- "ldsh;": "\u21b2",
- "le;": "\u2264",
- "leftarrow;": "\u2190",
- "leftarrowtail;": "\u21a2",
- "leftharpoondown;": "\u21bd",
- "leftharpoonup;": "\u21bc",
- "leftleftarrows;": "\u21c7",
- "leftrightarrow;": "\u2194",
- "leftrightarrows;": "\u21c6",
- "leftrightharpoons;": "\u21cb",
- "leftrightsquigarrow;": "\u21ad",
- "leftthreetimes;": "\u22cb",
- "leg;": "\u22da",
- "leq;": "\u2264",
- "leqq;": "\u2266",
- "leqslant;": "\u2a7d",
- "les;": "\u2a7d",
- "lescc;": "\u2aa8",
- "lesdot;": "\u2a7f",
- "lesdoto;": "\u2a81",
- "lesdotor;": "\u2a83",
- "lesg;": "\u22da\ufe00",
- "lesges;": "\u2a93",
- "lessapprox;": "\u2a85",
- "lessdot;": "\u22d6",
- "lesseqgtr;": "\u22da",
- "lesseqqgtr;": "\u2a8b",
- "lessgtr;": "\u2276",
- "lesssim;": "\u2272",
- "lfisht;": "\u297c",
- "lfloor;": "\u230a",
- "lfr;": "\U0001d529",
- "lg;": "\u2276",
- "lgE;": "\u2a91",
- "lhard;": "\u21bd",
- "lharu;": "\u21bc",
- "lharul;": "\u296a",
- "lhblk;": "\u2584",
- "ljcy;": "\u0459",
- "ll;": "\u226a",
- "llarr;": "\u21c7",
- "llcorner;": "\u231e",
- "llhard;": "\u296b",
- "lltri;": "\u25fa",
- "lmidot;": "\u0140",
- "lmoust;": "\u23b0",
- "lmoustache;": "\u23b0",
- "lnE;": "\u2268",
- "lnap;": "\u2a89",
- "lnapprox;": "\u2a89",
- "lne;": "\u2a87",
- "lneq;": "\u2a87",
- "lneqq;": "\u2268",
- "lnsim;": "\u22e6",
- "loang;": "\u27ec",
- "loarr;": "\u21fd",
- "lobrk;": "\u27e6",
- "longleftarrow;": "\u27f5",
- "longleftrightarrow;": "\u27f7",
- "longmapsto;": "\u27fc",
- "longrightarrow;": "\u27f6",
- "looparrowleft;": "\u21ab",
- "looparrowright;": "\u21ac",
- "lopar;": "\u2985",
- "lopf;": "\U0001d55d",
- "loplus;": "\u2a2d",
- "lotimes;": "\u2a34",
- "lowast;": "\u2217",
- "lowbar;": "_",
- "loz;": "\u25ca",
- "lozenge;": "\u25ca",
- "lozf;": "\u29eb",
- "lpar;": "(",
- "lparlt;": "\u2993",
- "lrarr;": "\u21c6",
- "lrcorner;": "\u231f",
- "lrhar;": "\u21cb",
- "lrhard;": "\u296d",
- "lrm;": "\u200e",
- "lrtri;": "\u22bf",
- "lsaquo;": "\u2039",
- "lscr;": "\U0001d4c1",
- "lsh;": "\u21b0",
- "lsim;": "\u2272",
- "lsime;": "\u2a8d",
- "lsimg;": "\u2a8f",
- "lsqb;": "[",
- "lsquo;": "\u2018",
- "lsquor;": "\u201a",
- "lstrok;": "\u0142",
- "lt": "<",
- "lt;": "<",
- "ltcc;": "\u2aa6",
- "ltcir;": "\u2a79",
- "ltdot;": "\u22d6",
- "lthree;": "\u22cb",
- "ltimes;": "\u22c9",
- "ltlarr;": "\u2976",
- "ltquest;": "\u2a7b",
- "ltrPar;": "\u2996",
- "ltri;": "\u25c3",
- "ltrie;": "\u22b4",
- "ltrif;": "\u25c2",
- "lurdshar;": "\u294a",
- "luruhar;": "\u2966",
- "lvertneqq;": "\u2268\ufe00",
- "lvnE;": "\u2268\ufe00",
- "mDDot;": "\u223a",
- "macr": "\xaf",
- "macr;": "\xaf",
- "male;": "\u2642",
- "malt;": "\u2720",
- "maltese;": "\u2720",
- "map;": "\u21a6",
- "mapsto;": "\u21a6",
- "mapstodown;": "\u21a7",
- "mapstoleft;": "\u21a4",
- "mapstoup;": "\u21a5",
- "marker;": "\u25ae",
- "mcomma;": "\u2a29",
- "mcy;": "\u043c",
- "mdash;": "\u2014",
- "measuredangle;": "\u2221",
- "mfr;": "\U0001d52a",
- "mho;": "\u2127",
- "micro": "\xb5",
- "micro;": "\xb5",
- "mid;": "\u2223",
- "midast;": "*",
- "midcir;": "\u2af0",
- "middot": "\xb7",
- "middot;": "\xb7",
- "minus;": "\u2212",
- "minusb;": "\u229f",
- "minusd;": "\u2238",
- "minusdu;": "\u2a2a",
- "mlcp;": "\u2adb",
- "mldr;": "\u2026",
- "mnplus;": "\u2213",
- "models;": "\u22a7",
- "mopf;": "\U0001d55e",
- "mp;": "\u2213",
- "mscr;": "\U0001d4c2",
- "mstpos;": "\u223e",
- "mu;": "\u03bc",
- "multimap;": "\u22b8",
- "mumap;": "\u22b8",
- "nGg;": "\u22d9\u0338",
- "nGt;": "\u226b\u20d2",
- "nGtv;": "\u226b\u0338",
- "nLeftarrow;": "\u21cd",
- "nLeftrightarrow;": "\u21ce",
- "nLl;": "\u22d8\u0338",
- "nLt;": "\u226a\u20d2",
- "nLtv;": "\u226a\u0338",
- "nRightarrow;": "\u21cf",
- "nVDash;": "\u22af",
- "nVdash;": "\u22ae",
- "nabla;": "\u2207",
- "nacute;": "\u0144",
- "nang;": "\u2220\u20d2",
- "nap;": "\u2249",
- "napE;": "\u2a70\u0338",
- "napid;": "\u224b\u0338",
- "napos;": "\u0149",
- "napprox;": "\u2249",
- "natur;": "\u266e",
- "natural;": "\u266e",
- "naturals;": "\u2115",
- "nbsp": "\xa0",
- "nbsp;": "\xa0",
- "nbump;": "\u224e\u0338",
- "nbumpe;": "\u224f\u0338",
- "ncap;": "\u2a43",
- "ncaron;": "\u0148",
- "ncedil;": "\u0146",
- "ncong;": "\u2247",
- "ncongdot;": "\u2a6d\u0338",
- "ncup;": "\u2a42",
- "ncy;": "\u043d",
- "ndash;": "\u2013",
- "ne;": "\u2260",
- "neArr;": "\u21d7",
- "nearhk;": "\u2924",
- "nearr;": "\u2197",
- "nearrow;": "\u2197",
- "nedot;": "\u2250\u0338",
- "nequiv;": "\u2262",
- "nesear;": "\u2928",
- "nesim;": "\u2242\u0338",
- "nexist;": "\u2204",
- "nexists;": "\u2204",
- "nfr;": "\U0001d52b",
- "ngE;": "\u2267\u0338",
- "nge;": "\u2271",
- "ngeq;": "\u2271",
- "ngeqq;": "\u2267\u0338",
- "ngeqslant;": "\u2a7e\u0338",
- "nges;": "\u2a7e\u0338",
- "ngsim;": "\u2275",
- "ngt;": "\u226f",
- "ngtr;": "\u226f",
- "nhArr;": "\u21ce",
- "nharr;": "\u21ae",
- "nhpar;": "\u2af2",
- "ni;": "\u220b",
- "nis;": "\u22fc",
- "nisd;": "\u22fa",
- "niv;": "\u220b",
- "njcy;": "\u045a",
- "nlArr;": "\u21cd",
- "nlE;": "\u2266\u0338",
- "nlarr;": "\u219a",
- "nldr;": "\u2025",
- "nle;": "\u2270",
- "nleftarrow;": "\u219a",
- "nleftrightarrow;": "\u21ae",
- "nleq;": "\u2270",
- "nleqq;": "\u2266\u0338",
- "nleqslant;": "\u2a7d\u0338",
- "nles;": "\u2a7d\u0338",
- "nless;": "\u226e",
- "nlsim;": "\u2274",
- "nlt;": "\u226e",
- "nltri;": "\u22ea",
- "nltrie;": "\u22ec",
- "nmid;": "\u2224",
- "nopf;": "\U0001d55f",
- "not": "\xac",
- "not;": "\xac",
- "notin;": "\u2209",
- "notinE;": "\u22f9\u0338",
- "notindot;": "\u22f5\u0338",
- "notinva;": "\u2209",
- "notinvb;": "\u22f7",
- "notinvc;": "\u22f6",
- "notni;": "\u220c",
- "notniva;": "\u220c",
- "notnivb;": "\u22fe",
- "notnivc;": "\u22fd",
- "npar;": "\u2226",
- "nparallel;": "\u2226",
- "nparsl;": "\u2afd\u20e5",
- "npart;": "\u2202\u0338",
- "npolint;": "\u2a14",
- "npr;": "\u2280",
- "nprcue;": "\u22e0",
- "npre;": "\u2aaf\u0338",
- "nprec;": "\u2280",
- "npreceq;": "\u2aaf\u0338",
- "nrArr;": "\u21cf",
- "nrarr;": "\u219b",
- "nrarrc;": "\u2933\u0338",
- "nrarrw;": "\u219d\u0338",
- "nrightarrow;": "\u219b",
- "nrtri;": "\u22eb",
- "nrtrie;": "\u22ed",
- "nsc;": "\u2281",
- "nsccue;": "\u22e1",
- "nsce;": "\u2ab0\u0338",
- "nscr;": "\U0001d4c3",
- "nshortmid;": "\u2224",
- "nshortparallel;": "\u2226",
- "nsim;": "\u2241",
- "nsime;": "\u2244",
- "nsimeq;": "\u2244",
- "nsmid;": "\u2224",
- "nspar;": "\u2226",
- "nsqsube;": "\u22e2",
- "nsqsupe;": "\u22e3",
- "nsub;": "\u2284",
- "nsubE;": "\u2ac5\u0338",
- "nsube;": "\u2288",
- "nsubset;": "\u2282\u20d2",
- "nsubseteq;": "\u2288",
- "nsubseteqq;": "\u2ac5\u0338",
- "nsucc;": "\u2281",
- "nsucceq;": "\u2ab0\u0338",
- "nsup;": "\u2285",
- "nsupE;": "\u2ac6\u0338",
- "nsupe;": "\u2289",
- "nsupset;": "\u2283\u20d2",
- "nsupseteq;": "\u2289",
- "nsupseteqq;": "\u2ac6\u0338",
- "ntgl;": "\u2279",
- "ntilde": "\xf1",
- "ntilde;": "\xf1",
- "ntlg;": "\u2278",
- "ntriangleleft;": "\u22ea",
- "ntrianglelefteq;": "\u22ec",
- "ntriangleright;": "\u22eb",
- "ntrianglerighteq;": "\u22ed",
- "nu;": "\u03bd",
- "num;": "#",
- "numero;": "\u2116",
- "numsp;": "\u2007",
- "nvDash;": "\u22ad",
- "nvHarr;": "\u2904",
- "nvap;": "\u224d\u20d2",
- "nvdash;": "\u22ac",
- "nvge;": "\u2265\u20d2",
- "nvgt;": ">\u20d2",
- "nvinfin;": "\u29de",
- "nvlArr;": "\u2902",
- "nvle;": "\u2264\u20d2",
- "nvlt;": "<\u20d2",
- "nvltrie;": "\u22b4\u20d2",
- "nvrArr;": "\u2903",
- "nvrtrie;": "\u22b5\u20d2",
- "nvsim;": "\u223c\u20d2",
- "nwArr;": "\u21d6",
- "nwarhk;": "\u2923",
- "nwarr;": "\u2196",
- "nwarrow;": "\u2196",
- "nwnear;": "\u2927",
- "oS;": "\u24c8",
- "oacute": "\xf3",
- "oacute;": "\xf3",
- "oast;": "\u229b",
- "ocir;": "\u229a",
- "ocirc": "\xf4",
- "ocirc;": "\xf4",
- "ocy;": "\u043e",
- "odash;": "\u229d",
- "odblac;": "\u0151",
- "odiv;": "\u2a38",
- "odot;": "\u2299",
- "odsold;": "\u29bc",
- "oelig;": "\u0153",
- "ofcir;": "\u29bf",
- "ofr;": "\U0001d52c",
- "ogon;": "\u02db",
- "ograve": "\xf2",
- "ograve;": "\xf2",
- "ogt;": "\u29c1",
- "ohbar;": "\u29b5",
- "ohm;": "\u03a9",
- "oint;": "\u222e",
- "olarr;": "\u21ba",
- "olcir;": "\u29be",
- "olcross;": "\u29bb",
- "oline;": "\u203e",
- "olt;": "\u29c0",
- "omacr;": "\u014d",
- "omega;": "\u03c9",
- "omicron;": "\u03bf",
- "omid;": "\u29b6",
- "ominus;": "\u2296",
- "oopf;": "\U0001d560",
- "opar;": "\u29b7",
- "operp;": "\u29b9",
- "oplus;": "\u2295",
- "or;": "\u2228",
- "orarr;": "\u21bb",
- "ord;": "\u2a5d",
- "order;": "\u2134",
- "orderof;": "\u2134",
- "ordf": "\xaa",
- "ordf;": "\xaa",
- "ordm": "\xba",
- "ordm;": "\xba",
- "origof;": "\u22b6",
- "oror;": "\u2a56",
- "orslope;": "\u2a57",
- "orv;": "\u2a5b",
- "oscr;": "\u2134",
- "oslash": "\xf8",
- "oslash;": "\xf8",
- "osol;": "\u2298",
- "otilde": "\xf5",
- "otilde;": "\xf5",
- "otimes;": "\u2297",
- "otimesas;": "\u2a36",
- "ouml": "\xf6",
- "ouml;": "\xf6",
- "ovbar;": "\u233d",
- "par;": "\u2225",
- "para": "\xb6",
- "para;": "\xb6",
- "parallel;": "\u2225",
- "parsim;": "\u2af3",
- "parsl;": "\u2afd",
- "part;": "\u2202",
- "pcy;": "\u043f",
- "percnt;": "%",
- "period;": ".",
- "permil;": "\u2030",
- "perp;": "\u22a5",
- "pertenk;": "\u2031",
- "pfr;": "\U0001d52d",
- "phi;": "\u03c6",
- "phiv;": "\u03d5",
- "phmmat;": "\u2133",
- "phone;": "\u260e",
- "pi;": "\u03c0",
- "pitchfork;": "\u22d4",
- "piv;": "\u03d6",
- "planck;": "\u210f",
- "planckh;": "\u210e",
- "plankv;": "\u210f",
- "plus;": "+",
- "plusacir;": "\u2a23",
- "plusb;": "\u229e",
- "pluscir;": "\u2a22",
- "plusdo;": "\u2214",
- "plusdu;": "\u2a25",
- "pluse;": "\u2a72",
- "plusmn": "\xb1",
- "plusmn;": "\xb1",
- "plussim;": "\u2a26",
- "plustwo;": "\u2a27",
- "pm;": "\xb1",
- "pointint;": "\u2a15",
- "popf;": "\U0001d561",
- "pound": "\xa3",
- "pound;": "\xa3",
- "pr;": "\u227a",
- "prE;": "\u2ab3",
- "prap;": "\u2ab7",
- "prcue;": "\u227c",
- "pre;": "\u2aaf",
- "prec;": "\u227a",
- "precapprox;": "\u2ab7",
- "preccurlyeq;": "\u227c",
- "preceq;": "\u2aaf",
- "precnapprox;": "\u2ab9",
- "precneqq;": "\u2ab5",
- "precnsim;": "\u22e8",
- "precsim;": "\u227e",
- "prime;": "\u2032",
- "primes;": "\u2119",
- "prnE;": "\u2ab5",
- "prnap;": "\u2ab9",
- "prnsim;": "\u22e8",
- "prod;": "\u220f",
- "profalar;": "\u232e",
- "profline;": "\u2312",
- "profsurf;": "\u2313",
- "prop;": "\u221d",
- "propto;": "\u221d",
- "prsim;": "\u227e",
- "prurel;": "\u22b0",
- "pscr;": "\U0001d4c5",
- "psi;": "\u03c8",
- "puncsp;": "\u2008",
- "qfr;": "\U0001d52e",
- "qint;": "\u2a0c",
- "qopf;": "\U0001d562",
- "qprime;": "\u2057",
- "qscr;": "\U0001d4c6",
- "quaternions;": "\u210d",
- "quatint;": "\u2a16",
- "quest;": "?",
- "questeq;": "\u225f",
- "quot": "\"",
- "quot;": "\"",
- "rAarr;": "\u21db",
- "rArr;": "\u21d2",
- "rAtail;": "\u291c",
- "rBarr;": "\u290f",
- "rHar;": "\u2964",
- "race;": "\u223d\u0331",
- "racute;": "\u0155",
- "radic;": "\u221a",
- "raemptyv;": "\u29b3",
- "rang;": "\u27e9",
- "rangd;": "\u2992",
- "range;": "\u29a5",
- "rangle;": "\u27e9",
- "raquo": "\xbb",
- "raquo;": "\xbb",
- "rarr;": "\u2192",
- "rarrap;": "\u2975",
- "rarrb;": "\u21e5",
- "rarrbfs;": "\u2920",
- "rarrc;": "\u2933",
- "rarrfs;": "\u291e",
- "rarrhk;": "\u21aa",
- "rarrlp;": "\u21ac",
- "rarrpl;": "\u2945",
- "rarrsim;": "\u2974",
- "rarrtl;": "\u21a3",
- "rarrw;": "\u219d",
- "ratail;": "\u291a",
- "ratio;": "\u2236",
- "rationals;": "\u211a",
- "rbarr;": "\u290d",
- "rbbrk;": "\u2773",
- "rbrace;": "}",
- "rbrack;": "]",
- "rbrke;": "\u298c",
- "rbrksld;": "\u298e",
- "rbrkslu;": "\u2990",
- "rcaron;": "\u0159",
- "rcedil;": "\u0157",
- "rceil;": "\u2309",
- "rcub;": "}",
- "rcy;": "\u0440",
- "rdca;": "\u2937",
- "rdldhar;": "\u2969",
- "rdquo;": "\u201d",
- "rdquor;": "\u201d",
- "rdsh;": "\u21b3",
- "real;": "\u211c",
- "realine;": "\u211b",
- "realpart;": "\u211c",
- "reals;": "\u211d",
- "rect;": "\u25ad",
- "reg": "\xae",
- "reg;": "\xae",
- "rfisht;": "\u297d",
- "rfloor;": "\u230b",
- "rfr;": "\U0001d52f",
- "rhard;": "\u21c1",
- "rharu;": "\u21c0",
- "rharul;": "\u296c",
- "rho;": "\u03c1",
- "rhov;": "\u03f1",
- "rightarrow;": "\u2192",
- "rightarrowtail;": "\u21a3",
- "rightharpoondown;": "\u21c1",
- "rightharpoonup;": "\u21c0",
- "rightleftarrows;": "\u21c4",
- "rightleftharpoons;": "\u21cc",
- "rightrightarrows;": "\u21c9",
- "rightsquigarrow;": "\u219d",
- "rightthreetimes;": "\u22cc",
- "ring;": "\u02da",
- "risingdotseq;": "\u2253",
- "rlarr;": "\u21c4",
- "rlhar;": "\u21cc",
- "rlm;": "\u200f",
- "rmoust;": "\u23b1",
- "rmoustache;": "\u23b1",
- "rnmid;": "\u2aee",
- "roang;": "\u27ed",
- "roarr;": "\u21fe",
- "robrk;": "\u27e7",
- "ropar;": "\u2986",
- "ropf;": "\U0001d563",
- "roplus;": "\u2a2e",
- "rotimes;": "\u2a35",
- "rpar;": ")",
- "rpargt;": "\u2994",
- "rppolint;": "\u2a12",
- "rrarr;": "\u21c9",
- "rsaquo;": "\u203a",
- "rscr;": "\U0001d4c7",
- "rsh;": "\u21b1",
- "rsqb;": "]",
- "rsquo;": "\u2019",
- "rsquor;": "\u2019",
- "rthree;": "\u22cc",
- "rtimes;": "\u22ca",
- "rtri;": "\u25b9",
- "rtrie;": "\u22b5",
- "rtrif;": "\u25b8",
- "rtriltri;": "\u29ce",
- "ruluhar;": "\u2968",
- "rx;": "\u211e",
- "sacute;": "\u015b",
- "sbquo;": "\u201a",
- "sc;": "\u227b",
- "scE;": "\u2ab4",
- "scap;": "\u2ab8",
- "scaron;": "\u0161",
- "sccue;": "\u227d",
- "sce;": "\u2ab0",
- "scedil;": "\u015f",
- "scirc;": "\u015d",
- "scnE;": "\u2ab6",
- "scnap;": "\u2aba",
- "scnsim;": "\u22e9",
- "scpolint;": "\u2a13",
- "scsim;": "\u227f",
- "scy;": "\u0441",
- "sdot;": "\u22c5",
- "sdotb;": "\u22a1",
- "sdote;": "\u2a66",
- "seArr;": "\u21d8",
- "searhk;": "\u2925",
- "searr;": "\u2198",
- "searrow;": "\u2198",
- "sect": "\xa7",
- "sect;": "\xa7",
- "semi;": ";",
- "seswar;": "\u2929",
- "setminus;": "\u2216",
- "setmn;": "\u2216",
- "sext;": "\u2736",
- "sfr;": "\U0001d530",
- "sfrown;": "\u2322",
- "sharp;": "\u266f",
- "shchcy;": "\u0449",
- "shcy;": "\u0448",
- "shortmid;": "\u2223",
- "shortparallel;": "\u2225",
- "shy": "\xad",
- "shy;": "\xad",
- "sigma;": "\u03c3",
- "sigmaf;": "\u03c2",
- "sigmav;": "\u03c2",
- "sim;": "\u223c",
- "simdot;": "\u2a6a",
- "sime;": "\u2243",
- "simeq;": "\u2243",
- "simg;": "\u2a9e",
- "simgE;": "\u2aa0",
- "siml;": "\u2a9d",
- "simlE;": "\u2a9f",
- "simne;": "\u2246",
- "simplus;": "\u2a24",
- "simrarr;": "\u2972",
- "slarr;": "\u2190",
- "smallsetminus;": "\u2216",
- "smashp;": "\u2a33",
- "smeparsl;": "\u29e4",
- "smid;": "\u2223",
- "smile;": "\u2323",
- "smt;": "\u2aaa",
- "smte;": "\u2aac",
- "smtes;": "\u2aac\ufe00",
- "softcy;": "\u044c",
- "sol;": "/",
- "solb;": "\u29c4",
- "solbar;": "\u233f",
- "sopf;": "\U0001d564",
- "spades;": "\u2660",
- "spadesuit;": "\u2660",
- "spar;": "\u2225",
- "sqcap;": "\u2293",
- "sqcaps;": "\u2293\ufe00",
- "sqcup;": "\u2294",
- "sqcups;": "\u2294\ufe00",
- "sqsub;": "\u228f",
- "sqsube;": "\u2291",
- "sqsubset;": "\u228f",
- "sqsubseteq;": "\u2291",
- "sqsup;": "\u2290",
- "sqsupe;": "\u2292",
- "sqsupset;": "\u2290",
- "sqsupseteq;": "\u2292",
- "squ;": "\u25a1",
- "square;": "\u25a1",
- "squarf;": "\u25aa",
- "squf;": "\u25aa",
- "srarr;": "\u2192",
- "sscr;": "\U0001d4c8",
- "ssetmn;": "\u2216",
- "ssmile;": "\u2323",
- "sstarf;": "\u22c6",
- "star;": "\u2606",
- "starf;": "\u2605",
- "straightepsilon;": "\u03f5",
- "straightphi;": "\u03d5",
- "strns;": "\xaf",
- "sub;": "\u2282",
- "subE;": "\u2ac5",
- "subdot;": "\u2abd",
- "sube;": "\u2286",
- "subedot;": "\u2ac3",
- "submult;": "\u2ac1",
- "subnE;": "\u2acb",
- "subne;": "\u228a",
- "subplus;": "\u2abf",
- "subrarr;": "\u2979",
- "subset;": "\u2282",
- "subseteq;": "\u2286",
- "subseteqq;": "\u2ac5",
- "subsetneq;": "\u228a",
- "subsetneqq;": "\u2acb",
- "subsim;": "\u2ac7",
- "subsub;": "\u2ad5",
- "subsup;": "\u2ad3",
- "succ;": "\u227b",
- "succapprox;": "\u2ab8",
- "succcurlyeq;": "\u227d",
- "succeq;": "\u2ab0",
- "succnapprox;": "\u2aba",
- "succneqq;": "\u2ab6",
- "succnsim;": "\u22e9",
- "succsim;": "\u227f",
- "sum;": "\u2211",
- "sung;": "\u266a",
- "sup1": "\xb9",
- "sup1;": "\xb9",
- "sup2": "\xb2",
- "sup2;": "\xb2",
- "sup3": "\xb3",
- "sup3;": "\xb3",
- "sup;": "\u2283",
- "supE;": "\u2ac6",
- "supdot;": "\u2abe",
- "supdsub;": "\u2ad8",
- "supe;": "\u2287",
- "supedot;": "\u2ac4",
- "suphsol;": "\u27c9",
- "suphsub;": "\u2ad7",
- "suplarr;": "\u297b",
- "supmult;": "\u2ac2",
- "supnE;": "\u2acc",
- "supne;": "\u228b",
- "supplus;": "\u2ac0",
- "supset;": "\u2283",
- "supseteq;": "\u2287",
- "supseteqq;": "\u2ac6",
- "supsetneq;": "\u228b",
- "supsetneqq;": "\u2acc",
- "supsim;": "\u2ac8",
- "supsub;": "\u2ad4",
- "supsup;": "\u2ad6",
- "swArr;": "\u21d9",
- "swarhk;": "\u2926",
- "swarr;": "\u2199",
- "swarrow;": "\u2199",
- "swnwar;": "\u292a",
- "szlig": "\xdf",
- "szlig;": "\xdf",
- "target;": "\u2316",
- "tau;": "\u03c4",
- "tbrk;": "\u23b4",
- "tcaron;": "\u0165",
- "tcedil;": "\u0163",
- "tcy;": "\u0442",
- "tdot;": "\u20db",
- "telrec;": "\u2315",
- "tfr;": "\U0001d531",
- "there4;": "\u2234",
- "therefore;": "\u2234",
- "theta;": "\u03b8",
- "thetasym;": "\u03d1",
- "thetav;": "\u03d1",
- "thickapprox;": "\u2248",
- "thicksim;": "\u223c",
- "thinsp;": "\u2009",
- "thkap;": "\u2248",
- "thksim;": "\u223c",
- "thorn": "\xfe",
- "thorn;": "\xfe",
- "tilde;": "\u02dc",
- "times": "\xd7",
- "times;": "\xd7",
- "timesb;": "\u22a0",
- "timesbar;": "\u2a31",
- "timesd;": "\u2a30",
- "tint;": "\u222d",
- "toea;": "\u2928",
- "top;": "\u22a4",
- "topbot;": "\u2336",
- "topcir;": "\u2af1",
- "topf;": "\U0001d565",
- "topfork;": "\u2ada",
- "tosa;": "\u2929",
- "tprime;": "\u2034",
- "trade;": "\u2122",
- "triangle;": "\u25b5",
- "triangledown;": "\u25bf",
- "triangleleft;": "\u25c3",
- "trianglelefteq;": "\u22b4",
- "triangleq;": "\u225c",
- "triangleright;": "\u25b9",
- "trianglerighteq;": "\u22b5",
- "tridot;": "\u25ec",
- "trie;": "\u225c",
- "triminus;": "\u2a3a",
- "triplus;": "\u2a39",
- "trisb;": "\u29cd",
- "tritime;": "\u2a3b",
- "trpezium;": "\u23e2",
- "tscr;": "\U0001d4c9",
- "tscy;": "\u0446",
- "tshcy;": "\u045b",
- "tstrok;": "\u0167",
- "twixt;": "\u226c",
- "twoheadleftarrow;": "\u219e",
- "twoheadrightarrow;": "\u21a0",
- "uArr;": "\u21d1",
- "uHar;": "\u2963",
- "uacute": "\xfa",
- "uacute;": "\xfa",
- "uarr;": "\u2191",
- "ubrcy;": "\u045e",
- "ubreve;": "\u016d",
- "ucirc": "\xfb",
- "ucirc;": "\xfb",
- "ucy;": "\u0443",
- "udarr;": "\u21c5",
- "udblac;": "\u0171",
- "udhar;": "\u296e",
- "ufisht;": "\u297e",
- "ufr;": "\U0001d532",
- "ugrave": "\xf9",
- "ugrave;": "\xf9",
- "uharl;": "\u21bf",
- "uharr;": "\u21be",
- "uhblk;": "\u2580",
- "ulcorn;": "\u231c",
- "ulcorner;": "\u231c",
- "ulcrop;": "\u230f",
- "ultri;": "\u25f8",
- "umacr;": "\u016b",
- "uml": "\xa8",
- "uml;": "\xa8",
- "uogon;": "\u0173",
- "uopf;": "\U0001d566",
- "uparrow;": "\u2191",
- "updownarrow;": "\u2195",
- "upharpoonleft;": "\u21bf",
- "upharpoonright;": "\u21be",
- "uplus;": "\u228e",
- "upsi;": "\u03c5",
- "upsih;": "\u03d2",
- "upsilon;": "\u03c5",
- "upuparrows;": "\u21c8",
- "urcorn;": "\u231d",
- "urcorner;": "\u231d",
- "urcrop;": "\u230e",
- "uring;": "\u016f",
- "urtri;": "\u25f9",
- "uscr;": "\U0001d4ca",
- "utdot;": "\u22f0",
- "utilde;": "\u0169",
- "utri;": "\u25b5",
- "utrif;": "\u25b4",
- "uuarr;": "\u21c8",
- "uuml": "\xfc",
- "uuml;": "\xfc",
- "uwangle;": "\u29a7",
- "vArr;": "\u21d5",
- "vBar;": "\u2ae8",
- "vBarv;": "\u2ae9",
- "vDash;": "\u22a8",
- "vangrt;": "\u299c",
- "varepsilon;": "\u03f5",
- "varkappa;": "\u03f0",
- "varnothing;": "\u2205",
- "varphi;": "\u03d5",
- "varpi;": "\u03d6",
- "varpropto;": "\u221d",
- "varr;": "\u2195",
- "varrho;": "\u03f1",
- "varsigma;": "\u03c2",
- "varsubsetneq;": "\u228a\ufe00",
- "varsubsetneqq;": "\u2acb\ufe00",
- "varsupsetneq;": "\u228b\ufe00",
- "varsupsetneqq;": "\u2acc\ufe00",
- "vartheta;": "\u03d1",
- "vartriangleleft;": "\u22b2",
- "vartriangleright;": "\u22b3",
- "vcy;": "\u0432",
- "vdash;": "\u22a2",
- "vee;": "\u2228",
- "veebar;": "\u22bb",
- "veeeq;": "\u225a",
- "vellip;": "\u22ee",
- "verbar;": "|",
- "vert;": "|",
- "vfr;": "\U0001d533",
- "vltri;": "\u22b2",
- "vnsub;": "\u2282\u20d2",
- "vnsup;": "\u2283\u20d2",
- "vopf;": "\U0001d567",
- "vprop;": "\u221d",
- "vrtri;": "\u22b3",
- "vscr;": "\U0001d4cb",
- "vsubnE;": "\u2acb\ufe00",
- "vsubne;": "\u228a\ufe00",
- "vsupnE;": "\u2acc\ufe00",
- "vsupne;": "\u228b\ufe00",
- "vzigzag;": "\u299a",
- "wcirc;": "\u0175",
- "wedbar;": "\u2a5f",
- "wedge;": "\u2227",
- "wedgeq;": "\u2259",
- "weierp;": "\u2118",
- "wfr;": "\U0001d534",
- "wopf;": "\U0001d568",
- "wp;": "\u2118",
- "wr;": "\u2240",
- "wreath;": "\u2240",
- "wscr;": "\U0001d4cc",
- "xcap;": "\u22c2",
- "xcirc;": "\u25ef",
- "xcup;": "\u22c3",
- "xdtri;": "\u25bd",
- "xfr;": "\U0001d535",
- "xhArr;": "\u27fa",
- "xharr;": "\u27f7",
- "xi;": "\u03be",
- "xlArr;": "\u27f8",
- "xlarr;": "\u27f5",
- "xmap;": "\u27fc",
- "xnis;": "\u22fb",
- "xodot;": "\u2a00",
- "xopf;": "\U0001d569",
- "xoplus;": "\u2a01",
- "xotime;": "\u2a02",
- "xrArr;": "\u27f9",
- "xrarr;": "\u27f6",
- "xscr;": "\U0001d4cd",
- "xsqcup;": "\u2a06",
- "xuplus;": "\u2a04",
- "xutri;": "\u25b3",
- "xvee;": "\u22c1",
- "xwedge;": "\u22c0",
- "yacute": "\xfd",
- "yacute;": "\xfd",
- "yacy;": "\u044f",
- "ycirc;": "\u0177",
- "ycy;": "\u044b",
- "yen": "\xa5",
- "yen;": "\xa5",
- "yfr;": "\U0001d536",
- "yicy;": "\u0457",
- "yopf;": "\U0001d56a",
- "yscr;": "\U0001d4ce",
- "yucy;": "\u044e",
- "yuml": "\xff",
- "yuml;": "\xff",
- "zacute;": "\u017a",
- "zcaron;": "\u017e",
- "zcy;": "\u0437",
- "zdot;": "\u017c",
- "zeetrf;": "\u2128",
- "zeta;": "\u03b6",
- "zfr;": "\U0001d537",
- "zhcy;": "\u0436",
- "zigrarr;": "\u21dd",
- "zopf;": "\U0001d56b",
- "zscr;": "\U0001d4cf",
- "zwj;": "\u200d",
- "zwnj;": "\u200c",
-}
-
-replacementCharacters = {
- 0x0: "\uFFFD",
- 0x0d: "\u000D",
- 0x80: "\u20AC",
- 0x81: "\u0081",
- 0x82: "\u201A",
- 0x83: "\u0192",
- 0x84: "\u201E",
- 0x85: "\u2026",
- 0x86: "\u2020",
- 0x87: "\u2021",
- 0x88: "\u02C6",
- 0x89: "\u2030",
- 0x8A: "\u0160",
- 0x8B: "\u2039",
- 0x8C: "\u0152",
- 0x8D: "\u008D",
- 0x8E: "\u017D",
- 0x8F: "\u008F",
- 0x90: "\u0090",
- 0x91: "\u2018",
- 0x92: "\u2019",
- 0x93: "\u201C",
- 0x94: "\u201D",
- 0x95: "\u2022",
- 0x96: "\u2013",
- 0x97: "\u2014",
- 0x98: "\u02DC",
- 0x99: "\u2122",
- 0x9A: "\u0161",
- 0x9B: "\u203A",
- 0x9C: "\u0153",
- 0x9D: "\u009D",
- 0x9E: "\u017E",
- 0x9F: "\u0178",
-}
-
-tokenTypes = {
- "Doctype": 0,
- "Characters": 1,
- "SpaceCharacters": 2,
- "StartTag": 3,
- "EndTag": 4,
- "EmptyTag": 5,
- "Comment": 6,
- "ParseError": 7
-}
-
-tagTokenTypes = frozenset([tokenTypes["StartTag"], tokenTypes["EndTag"],
- tokenTypes["EmptyTag"]])
-
-
-prefixes = {v: k for k, v in namespaces.items()}
-prefixes["http://www.w3.org/1998/Math/MathML"] = "math"
-
-
-class DataLossWarning(UserWarning):
- """Raised when the current tree is unable to represent the input data"""
- pass
-
-
-class _ReparseException(Exception):
- pass
diff --git a/src/pip/_vendor/html5lib/filters/alphabeticalattributes.py b/src/pip/_vendor/html5lib/filters/alphabeticalattributes.py
deleted file mode 100644
index 5ba926e3b..000000000
--- a/src/pip/_vendor/html5lib/filters/alphabeticalattributes.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-from . import base
-
-from collections import OrderedDict
-
-
-def _attr_key(attr):
- """Return an appropriate key for an attribute for sorting
-
- Attributes have a namespace that can be either ``None`` or a string. We
- can't compare the two because they're different types, so we convert
- ``None`` to an empty string first.
-
- """
- return (attr[0][0] or ''), attr[0][1]
-
-
-class Filter(base.Filter):
- """Alphabetizes attributes for elements"""
- def __iter__(self):
- for token in base.Filter.__iter__(self):
- if token["type"] in ("StartTag", "EmptyTag"):
- attrs = OrderedDict()
- for name, value in sorted(token["data"].items(),
- key=_attr_key):
- attrs[name] = value
- token["data"] = attrs
- yield token
diff --git a/src/pip/_vendor/html5lib/filters/base.py b/src/pip/_vendor/html5lib/filters/base.py
deleted file mode 100644
index c7dbaed0f..000000000
--- a/src/pip/_vendor/html5lib/filters/base.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-
-class Filter(object):
- def __init__(self, source):
- self.source = source
-
- def __iter__(self):
- return iter(self.source)
-
- def __getattr__(self, name):
- return getattr(self.source, name)
diff --git a/src/pip/_vendor/html5lib/filters/inject_meta_charset.py b/src/pip/_vendor/html5lib/filters/inject_meta_charset.py
deleted file mode 100644
index aefb5c842..000000000
--- a/src/pip/_vendor/html5lib/filters/inject_meta_charset.py
+++ /dev/null
@@ -1,73 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-from . import base
-
-
-class Filter(base.Filter):
- """Injects ``<meta charset=ENCODING>`` tag into head of document"""
- def __init__(self, source, encoding):
- """Creates a Filter
-
- :arg source: the source token stream
-
- :arg encoding: the encoding to set
-
- """
- base.Filter.__init__(self, source)
- self.encoding = encoding
-
- def __iter__(self):
- state = "pre_head"
- meta_found = (self.encoding is None)
- pending = []
-
- for token in base.Filter.__iter__(self):
- type = token["type"]
- if type == "StartTag":
- if token["name"].lower() == "head":
- state = "in_head"
-
- elif type == "EmptyTag":
- if token["name"].lower() == "meta":
- # replace charset with actual encoding
- has_http_equiv_content_type = False
- for (namespace, name), value in token["data"].items():
- if namespace is not None:
- continue
- elif name.lower() == 'charset':
- token["data"][(namespace, name)] = self.encoding
- meta_found = True
- break
- elif name == 'http-equiv' and value.lower() == 'content-type':
- has_http_equiv_content_type = True
- else:
- if has_http_equiv_content_type and (None, "content") in token["data"]:
- token["data"][(None, "content")] = 'text/html; charset=%s' % self.encoding
- meta_found = True
-
- elif token["name"].lower() == "head" and not meta_found:
- # insert meta into empty head
- yield {"type": "StartTag", "name": "head",
- "data": token["data"]}
- yield {"type": "EmptyTag", "name": "meta",
- "data": {(None, "charset"): self.encoding}}
- yield {"type": "EndTag", "name": "head"}
- meta_found = True
- continue
-
- elif type == "EndTag":
- if token["name"].lower() == "head" and pending:
- # insert meta into head (if necessary) and flush pending queue
- yield pending.pop(0)
- if not meta_found:
- yield {"type": "EmptyTag", "name": "meta",
- "data": {(None, "charset"): self.encoding}}
- while pending:
- yield pending.pop(0)
- meta_found = True
- state = "post_head"
-
- if state == "in_head":
- pending.append(token)
- else:
- yield token
diff --git a/src/pip/_vendor/html5lib/filters/lint.py b/src/pip/_vendor/html5lib/filters/lint.py
deleted file mode 100644
index fcc07eec5..000000000
--- a/src/pip/_vendor/html5lib/filters/lint.py
+++ /dev/null
@@ -1,93 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-from pip._vendor.six import text_type
-
-from . import base
-from ..constants import namespaces, voidElements
-
-from ..constants import spaceCharacters
-spaceCharacters = "".join(spaceCharacters)
-
-
-class Filter(base.Filter):
- """Lints the token stream for errors
-
- If it finds any errors, it'll raise an ``AssertionError``.
-
- """
- def __init__(self, source, require_matching_tags=True):
- """Creates a Filter
-
- :arg source: the source token stream
-
- :arg require_matching_tags: whether or not to require matching tags
-
- """
- super(Filter, self).__init__(source)
- self.require_matching_tags = require_matching_tags
-
- def __iter__(self):
- open_elements = []
- for token in base.Filter.__iter__(self):
- type = token["type"]
- if type in ("StartTag", "EmptyTag"):
- namespace = token["namespace"]
- name = token["name"]
- assert namespace is None or isinstance(namespace, text_type)
- assert namespace != ""
- assert isinstance(name, text_type)
- assert name != ""
- assert isinstance(token["data"], dict)
- if (not namespace or namespace == namespaces["html"]) and name in voidElements:
- assert type == "EmptyTag"
- else:
- assert type == "StartTag"
- if type == "StartTag" and self.require_matching_tags:
- open_elements.append((namespace, name))
- for (namespace, name), value in token["data"].items():
- assert namespace is None or isinstance(namespace, text_type)
- assert namespace != ""
- assert isinstance(name, text_type)
- assert name != ""
- assert isinstance(value, text_type)
-
- elif type == "EndTag":
- namespace = token["namespace"]
- name = token["name"]
- assert namespace is None or isinstance(namespace, text_type)
- assert namespace != ""
- assert isinstance(name, text_type)
- assert name != ""
- if (not namespace or namespace == namespaces["html"]) and name in voidElements:
- assert False, "Void element reported as EndTag token: %(tag)s" % {"tag": name}
- elif self.require_matching_tags:
- start = open_elements.pop()
- assert start == (namespace, name)
-
- elif type == "Comment":
- data = token["data"]
- assert isinstance(data, text_type)
-
- elif type in ("Characters", "SpaceCharacters"):
- data = token["data"]
- assert isinstance(data, text_type)
- assert data != ""
- if type == "SpaceCharacters":
- assert data.strip(spaceCharacters) == ""
-
- elif type == "Doctype":
- name = token["name"]
- assert name is None or isinstance(name, text_type)
- assert token["publicId"] is None or isinstance(name, text_type)
- assert token["systemId"] is None or isinstance(name, text_type)
-
- elif type == "Entity":
- assert isinstance(token["name"], text_type)
-
- elif type == "SerializerError":
- assert isinstance(token["data"], text_type)
-
- else:
- assert False, "Unknown token type: %(type)s" % {"type": type}
-
- yield token
diff --git a/src/pip/_vendor/html5lib/filters/optionaltags.py b/src/pip/_vendor/html5lib/filters/optionaltags.py
deleted file mode 100644
index 4a865012c..000000000
--- a/src/pip/_vendor/html5lib/filters/optionaltags.py
+++ /dev/null
@@ -1,207 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-from . import base
-
-
-class Filter(base.Filter):
- """Removes optional tags from the token stream"""
- def slider(self):
- previous1 = previous2 = None
- for token in self.source:
- if previous1 is not None:
- yield previous2, previous1, token
- previous2 = previous1
- previous1 = token
- if previous1 is not None:
- yield previous2, previous1, None
-
- def __iter__(self):
- for previous, token, next in self.slider():
- type = token["type"]
- if type == "StartTag":
- if (token["data"] or
- not self.is_optional_start(token["name"], previous, next)):
- yield token
- elif type == "EndTag":
- if not self.is_optional_end(token["name"], next):
- yield token
- else:
- yield token
-
- def is_optional_start(self, tagname, previous, next):
- type = next and next["type"] or None
- if tagname in 'html':
- # An html element's start tag may be omitted if the first thing
- # inside the html element is not a space character or a comment.
- return type not in ("Comment", "SpaceCharacters")
- elif tagname == 'head':
- # A head element's start tag may be omitted if the first thing
- # inside the head element is an element.
- # XXX: we also omit the start tag if the head element is empty
- if type in ("StartTag", "EmptyTag"):
- return True
- elif type == "EndTag":
- return next["name"] == "head"
- elif tagname == 'body':
- # A body element's start tag may be omitted if the first thing
- # inside the body element is not a space character or a comment,
- # except if the first thing inside the body element is a script
- # or style element and the node immediately preceding the body
- # element is a head element whose end tag has been omitted.
- if type in ("Comment", "SpaceCharacters"):
- return False
- elif type == "StartTag":
- # XXX: we do not look at the preceding event, so we never omit
- # the body element's start tag if it's followed by a script or
- # a style element.
- return next["name"] not in ('script', 'style')
- else:
- return True
- elif tagname == 'colgroup':
- # A colgroup element's start tag may be omitted if the first thing
- # inside the colgroup element is a col element, and if the element
- # is not immediately preceded by another colgroup element whose
- # end tag has been omitted.
- if type in ("StartTag", "EmptyTag"):
- # XXX: we do not look at the preceding event, so instead we never
- # omit the colgroup element's end tag when it is immediately
- # followed by another colgroup element. See is_optional_end.
- return next["name"] == "col"
- else:
- return False
- elif tagname == 'tbody':
- # A tbody element's start tag may be omitted if the first thing
- # inside the tbody element is a tr element, and if the element is
- # not immediately preceded by a tbody, thead, or tfoot element
- # whose end tag has been omitted.
- if type == "StartTag":
- # omit the thead and tfoot elements' end tag when they are
- # immediately followed by a tbody element. See is_optional_end.
- if previous and previous['type'] == 'EndTag' and \
- previous['name'] in ('tbody', 'thead', 'tfoot'):
- return False
- return next["name"] == 'tr'
- else:
- return False
- return False
-
- def is_optional_end(self, tagname, next):
- type = next and next["type"] or None
- if tagname in ('html', 'head', 'body'):
- # An html element's end tag may be omitted if the html element
- # is not immediately followed by a space character or a comment.
- return type not in ("Comment", "SpaceCharacters")
- elif tagname in ('li', 'optgroup', 'tr'):
- # A li element's end tag may be omitted if the li element is
- # immediately followed by another li element or if there is
- # no more content in the parent element.
- # An optgroup element's end tag may be omitted if the optgroup
- # element is immediately followed by another optgroup element,
- # or if there is no more content in the parent element.
- # A tr element's end tag may be omitted if the tr element is
- # immediately followed by another tr element, or if there is
- # no more content in the parent element.
- if type == "StartTag":
- return next["name"] == tagname
- else:
- return type == "EndTag" or type is None
- elif tagname in ('dt', 'dd'):
- # A dt element's end tag may be omitted if the dt element is
- # immediately followed by another dt element or a dd element.
- # A dd element's end tag may be omitted if the dd element is
- # immediately followed by another dd element or a dt element,
- # or if there is no more content in the parent element.
- if type == "StartTag":
- return next["name"] in ('dt', 'dd')
- elif tagname == 'dd':
- return type == "EndTag" or type is None
- else:
- return False
- elif tagname == 'p':
- # A p element's end tag may be omitted if the p element is
- # immediately followed by an address, article, aside,
- # blockquote, datagrid, dialog, dir, div, dl, fieldset,
- # footer, form, h1, h2, h3, h4, h5, h6, header, hr, menu,
- # nav, ol, p, pre, section, table, or ul, element, or if
- # there is no more content in the parent element.
- if type in ("StartTag", "EmptyTag"):
- return next["name"] in ('address', 'article', 'aside',
- 'blockquote', 'datagrid', 'dialog',
- 'dir', 'div', 'dl', 'fieldset', 'footer',
- 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
- 'header', 'hr', 'menu', 'nav', 'ol',
- 'p', 'pre', 'section', 'table', 'ul')
- else:
- return type == "EndTag" or type is None
- elif tagname == 'option':
- # An option element's end tag may be omitted if the option
- # element is immediately followed by another option element,
- # or if it is immediately followed by an <code>optgroup</code>
- # element, or if there is no more content in the parent
- # element.
- if type == "StartTag":
- return next["name"] in ('option', 'optgroup')
- else:
- return type == "EndTag" or type is None
- elif tagname in ('rt', 'rp'):
- # An rt element's end tag may be omitted if the rt element is
- # immediately followed by an rt or rp element, or if there is
- # no more content in the parent element.
- # An rp element's end tag may be omitted if the rp element is
- # immediately followed by an rt or rp element, or if there is
- # no more content in the parent element.
- if type == "StartTag":
- return next["name"] in ('rt', 'rp')
- else:
- return type == "EndTag" or type is None
- elif tagname == 'colgroup':
- # A colgroup element's end tag may be omitted if the colgroup
- # element is not immediately followed by a space character or
- # a comment.
- if type in ("Comment", "SpaceCharacters"):
- return False
- elif type == "StartTag":
- # XXX: we also look for an immediately following colgroup
- # element. See is_optional_start.
- return next["name"] != 'colgroup'
- else:
- return True
- elif tagname in ('thead', 'tbody'):
- # A thead element's end tag may be omitted if the thead element
- # is immediately followed by a tbody or tfoot element.
- # A tbody element's end tag may be omitted if the tbody element
- # is immediately followed by a tbody or tfoot element, or if
- # there is no more content in the parent element.
- # A tfoot element's end tag may be omitted if the tfoot element
- # is immediately followed by a tbody element, or if there is no
- # more content in the parent element.
- # XXX: we never omit the end tag when the following element is
- # a tbody. See is_optional_start.
- if type == "StartTag":
- return next["name"] in ['tbody', 'tfoot']
- elif tagname == 'tbody':
- return type == "EndTag" or type is None
- else:
- return False
- elif tagname == 'tfoot':
- # A tfoot element's end tag may be omitted if the tfoot element
- # is immediately followed by a tbody element, or if there is no
- # more content in the parent element.
- # XXX: we never omit the end tag when the following element is
- # a tbody. See is_optional_start.
- if type == "StartTag":
- return next["name"] == 'tbody'
- else:
- return type == "EndTag" or type is None
- elif tagname in ('td', 'th'):
- # A td element's end tag may be omitted if the td element is
- # immediately followed by a td or th element, or if there is
- # no more content in the parent element.
- # A th element's end tag may be omitted if the th element is
- # immediately followed by a td or th element, or if there is
- # no more content in the parent element.
- if type == "StartTag":
- return next["name"] in ('td', 'th')
- else:
- return type == "EndTag" or type is None
- return False
diff --git a/src/pip/_vendor/html5lib/filters/sanitizer.py b/src/pip/_vendor/html5lib/filters/sanitizer.py
deleted file mode 100644
index aa7431d13..000000000
--- a/src/pip/_vendor/html5lib/filters/sanitizer.py
+++ /dev/null
@@ -1,916 +0,0 @@
-"""Deprecated from html5lib 1.1.
-
-See `here <https://github.com/html5lib/html5lib-python/issues/443>`_ for
-information about its deprecation; `Bleach <https://github.com/mozilla/bleach>`_
-is recommended as a replacement. Please let us know in the aforementioned issue
-if Bleach is unsuitable for your needs.
-
-"""
-from __future__ import absolute_import, division, unicode_literals
-
-import re
-import warnings
-from xml.sax.saxutils import escape, unescape
-
-from pip._vendor.six.moves import urllib_parse as urlparse
-
-from . import base
-from ..constants import namespaces, prefixes
-
-__all__ = ["Filter"]
-
-
-_deprecation_msg = (
- "html5lib's sanitizer is deprecated; see " +
- "https://github.com/html5lib/html5lib-python/issues/443 and please let " +
- "us know if Bleach is unsuitable for your needs"
-)
-
-warnings.warn(_deprecation_msg, DeprecationWarning)
-
-allowed_elements = frozenset((
- (namespaces['html'], 'a'),
- (namespaces['html'], 'abbr'),
- (namespaces['html'], 'acronym'),
- (namespaces['html'], 'address'),
- (namespaces['html'], 'area'),
- (namespaces['html'], 'article'),
- (namespaces['html'], 'aside'),
- (namespaces['html'], 'audio'),
- (namespaces['html'], 'b'),
- (namespaces['html'], 'big'),
- (namespaces['html'], 'blockquote'),
- (namespaces['html'], 'br'),
- (namespaces['html'], 'button'),
- (namespaces['html'], 'canvas'),
- (namespaces['html'], 'caption'),
- (namespaces['html'], 'center'),
- (namespaces['html'], 'cite'),
- (namespaces['html'], 'code'),
- (namespaces['html'], 'col'),
- (namespaces['html'], 'colgroup'),
- (namespaces['html'], 'command'),
- (namespaces['html'], 'datagrid'),
- (namespaces['html'], 'datalist'),
- (namespaces['html'], 'dd'),
- (namespaces['html'], 'del'),
- (namespaces['html'], 'details'),
- (namespaces['html'], 'dfn'),
- (namespaces['html'], 'dialog'),
- (namespaces['html'], 'dir'),
- (namespaces['html'], 'div'),
- (namespaces['html'], 'dl'),
- (namespaces['html'], 'dt'),
- (namespaces['html'], 'em'),
- (namespaces['html'], 'event-source'),
- (namespaces['html'], 'fieldset'),
- (namespaces['html'], 'figcaption'),
- (namespaces['html'], 'figure'),
- (namespaces['html'], 'footer'),
- (namespaces['html'], 'font'),
- (namespaces['html'], 'form'),
- (namespaces['html'], 'header'),
- (namespaces['html'], 'h1'),
- (namespaces['html'], 'h2'),
- (namespaces['html'], 'h3'),
- (namespaces['html'], 'h4'),
- (namespaces['html'], 'h5'),
- (namespaces['html'], 'h6'),
- (namespaces['html'], 'hr'),
- (namespaces['html'], 'i'),
- (namespaces['html'], 'img'),
- (namespaces['html'], 'input'),
- (namespaces['html'], 'ins'),
- (namespaces['html'], 'keygen'),
- (namespaces['html'], 'kbd'),
- (namespaces['html'], 'label'),
- (namespaces['html'], 'legend'),
- (namespaces['html'], 'li'),
- (namespaces['html'], 'm'),
- (namespaces['html'], 'map'),
- (namespaces['html'], 'menu'),
- (namespaces['html'], 'meter'),
- (namespaces['html'], 'multicol'),
- (namespaces['html'], 'nav'),
- (namespaces['html'], 'nextid'),
- (namespaces['html'], 'ol'),
- (namespaces['html'], 'output'),
- (namespaces['html'], 'optgroup'),
- (namespaces['html'], 'option'),
- (namespaces['html'], 'p'),
- (namespaces['html'], 'pre'),
- (namespaces['html'], 'progress'),
- (namespaces['html'], 'q'),
- (namespaces['html'], 's'),
- (namespaces['html'], 'samp'),
- (namespaces['html'], 'section'),
- (namespaces['html'], 'select'),
- (namespaces['html'], 'small'),
- (namespaces['html'], 'sound'),
- (namespaces['html'], 'source'),
- (namespaces['html'], 'spacer'),
- (namespaces['html'], 'span'),
- (namespaces['html'], 'strike'),
- (namespaces['html'], 'strong'),
- (namespaces['html'], 'sub'),
- (namespaces['html'], 'sup'),
- (namespaces['html'], 'table'),
- (namespaces['html'], 'tbody'),
- (namespaces['html'], 'td'),
- (namespaces['html'], 'textarea'),
- (namespaces['html'], 'time'),
- (namespaces['html'], 'tfoot'),
- (namespaces['html'], 'th'),
- (namespaces['html'], 'thead'),
- (namespaces['html'], 'tr'),
- (namespaces['html'], 'tt'),
- (namespaces['html'], 'u'),
- (namespaces['html'], 'ul'),
- (namespaces['html'], 'var'),
- (namespaces['html'], 'video'),
- (namespaces['mathml'], 'maction'),
- (namespaces['mathml'], 'math'),
- (namespaces['mathml'], 'merror'),
- (namespaces['mathml'], 'mfrac'),
- (namespaces['mathml'], 'mi'),
- (namespaces['mathml'], 'mmultiscripts'),
- (namespaces['mathml'], 'mn'),
- (namespaces['mathml'], 'mo'),
- (namespaces['mathml'], 'mover'),
- (namespaces['mathml'], 'mpadded'),
- (namespaces['mathml'], 'mphantom'),
- (namespaces['mathml'], 'mprescripts'),
- (namespaces['mathml'], 'mroot'),
- (namespaces['mathml'], 'mrow'),
- (namespaces['mathml'], 'mspace'),
- (namespaces['mathml'], 'msqrt'),
- (namespaces['mathml'], 'mstyle'),
- (namespaces['mathml'], 'msub'),
- (namespaces['mathml'], 'msubsup'),
- (namespaces['mathml'], 'msup'),
- (namespaces['mathml'], 'mtable'),
- (namespaces['mathml'], 'mtd'),
- (namespaces['mathml'], 'mtext'),
- (namespaces['mathml'], 'mtr'),
- (namespaces['mathml'], 'munder'),
- (namespaces['mathml'], 'munderover'),
- (namespaces['mathml'], 'none'),
- (namespaces['svg'], 'a'),
- (namespaces['svg'], 'animate'),
- (namespaces['svg'], 'animateColor'),
- (namespaces['svg'], 'animateMotion'),
- (namespaces['svg'], 'animateTransform'),
- (namespaces['svg'], 'clipPath'),
- (namespaces['svg'], 'circle'),
- (namespaces['svg'], 'defs'),
- (namespaces['svg'], 'desc'),
- (namespaces['svg'], 'ellipse'),
- (namespaces['svg'], 'font-face'),
- (namespaces['svg'], 'font-face-name'),
- (namespaces['svg'], 'font-face-src'),
- (namespaces['svg'], 'g'),
- (namespaces['svg'], 'glyph'),
- (namespaces['svg'], 'hkern'),
- (namespaces['svg'], 'linearGradient'),
- (namespaces['svg'], 'line'),
- (namespaces['svg'], 'marker'),
- (namespaces['svg'], 'metadata'),
- (namespaces['svg'], 'missing-glyph'),
- (namespaces['svg'], 'mpath'),
- (namespaces['svg'], 'path'),
- (namespaces['svg'], 'polygon'),
- (namespaces['svg'], 'polyline'),
- (namespaces['svg'], 'radialGradient'),
- (namespaces['svg'], 'rect'),
- (namespaces['svg'], 'set'),
- (namespaces['svg'], 'stop'),
- (namespaces['svg'], 'svg'),
- (namespaces['svg'], 'switch'),
- (namespaces['svg'], 'text'),
- (namespaces['svg'], 'title'),
- (namespaces['svg'], 'tspan'),
- (namespaces['svg'], 'use'),
-))
-
-allowed_attributes = frozenset((
- # HTML attributes
- (None, 'abbr'),
- (None, 'accept'),
- (None, 'accept-charset'),
- (None, 'accesskey'),
- (None, 'action'),
- (None, 'align'),
- (None, 'alt'),
- (None, 'autocomplete'),
- (None, 'autofocus'),
- (None, 'axis'),
- (None, 'background'),
- (None, 'balance'),
- (None, 'bgcolor'),
- (None, 'bgproperties'),
- (None, 'border'),
- (None, 'bordercolor'),
- (None, 'bordercolordark'),
- (None, 'bordercolorlight'),
- (None, 'bottompadding'),
- (None, 'cellpadding'),
- (None, 'cellspacing'),
- (None, 'ch'),
- (None, 'challenge'),
- (None, 'char'),
- (None, 'charoff'),
- (None, 'choff'),
- (None, 'charset'),
- (None, 'checked'),
- (None, 'cite'),
- (None, 'class'),
- (None, 'clear'),
- (None, 'color'),
- (None, 'cols'),
- (None, 'colspan'),
- (None, 'compact'),
- (None, 'contenteditable'),
- (None, 'controls'),
- (None, 'coords'),
- (None, 'data'),
- (None, 'datafld'),
- (None, 'datapagesize'),
- (None, 'datasrc'),
- (None, 'datetime'),
- (None, 'default'),
- (None, 'delay'),
- (None, 'dir'),
- (None, 'disabled'),
- (None, 'draggable'),
- (None, 'dynsrc'),
- (None, 'enctype'),
- (None, 'end'),
- (None, 'face'),
- (None, 'for'),
- (None, 'form'),
- (None, 'frame'),
- (None, 'galleryimg'),
- (None, 'gutter'),
- (None, 'headers'),
- (None, 'height'),
- (None, 'hidefocus'),
- (None, 'hidden'),
- (None, 'high'),
- (None, 'href'),
- (None, 'hreflang'),
- (None, 'hspace'),
- (None, 'icon'),
- (None, 'id'),
- (None, 'inputmode'),
- (None, 'ismap'),
- (None, 'keytype'),
- (None, 'label'),
- (None, 'leftspacing'),
- (None, 'lang'),
- (None, 'list'),
- (None, 'longdesc'),
- (None, 'loop'),
- (None, 'loopcount'),
- (None, 'loopend'),
- (None, 'loopstart'),
- (None, 'low'),
- (None, 'lowsrc'),
- (None, 'max'),
- (None, 'maxlength'),
- (None, 'media'),
- (None, 'method'),
- (None, 'min'),
- (None, 'multiple'),
- (None, 'name'),
- (None, 'nohref'),
- (None, 'noshade'),
- (None, 'nowrap'),
- (None, 'open'),
- (None, 'optimum'),
- (None, 'pattern'),
- (None, 'ping'),
- (None, 'point-size'),
- (None, 'poster'),
- (None, 'pqg'),
- (None, 'preload'),
- (None, 'prompt'),
- (None, 'radiogroup'),
- (None, 'readonly'),
- (None, 'rel'),
- (None, 'repeat-max'),
- (None, 'repeat-min'),
- (None, 'replace'),
- (None, 'required'),
- (None, 'rev'),
- (None, 'rightspacing'),
- (None, 'rows'),
- (None, 'rowspan'),
- (None, 'rules'),
- (None, 'scope'),
- (None, 'selected'),
- (None, 'shape'),
- (None, 'size'),
- (None, 'span'),
- (None, 'src'),
- (None, 'start'),
- (None, 'step'),
- (None, 'style'),
- (None, 'summary'),
- (None, 'suppress'),
- (None, 'tabindex'),
- (None, 'target'),
- (None, 'template'),
- (None, 'title'),
- (None, 'toppadding'),
- (None, 'type'),
- (None, 'unselectable'),
- (None, 'usemap'),
- (None, 'urn'),
- (None, 'valign'),
- (None, 'value'),
- (None, 'variable'),
- (None, 'volume'),
- (None, 'vspace'),
- (None, 'vrml'),
- (None, 'width'),
- (None, 'wrap'),
- (namespaces['xml'], 'lang'),
- # MathML attributes
- (None, 'actiontype'),
- (None, 'align'),
- (None, 'columnalign'),
- (None, 'columnalign'),
- (None, 'columnalign'),
- (None, 'columnlines'),
- (None, 'columnspacing'),
- (None, 'columnspan'),
- (None, 'depth'),
- (None, 'display'),
- (None, 'displaystyle'),
- (None, 'equalcolumns'),
- (None, 'equalrows'),
- (None, 'fence'),
- (None, 'fontstyle'),
- (None, 'fontweight'),
- (None, 'frame'),
- (None, 'height'),
- (None, 'linethickness'),
- (None, 'lspace'),
- (None, 'mathbackground'),
- (None, 'mathcolor'),
- (None, 'mathvariant'),
- (None, 'mathvariant'),
- (None, 'maxsize'),
- (None, 'minsize'),
- (None, 'other'),
- (None, 'rowalign'),
- (None, 'rowalign'),
- (None, 'rowalign'),
- (None, 'rowlines'),
- (None, 'rowspacing'),
- (None, 'rowspan'),
- (None, 'rspace'),
- (None, 'scriptlevel'),
- (None, 'selection'),
- (None, 'separator'),
- (None, 'stretchy'),
- (None, 'width'),
- (None, 'width'),
- (namespaces['xlink'], 'href'),
- (namespaces['xlink'], 'show'),
- (namespaces['xlink'], 'type'),
- # SVG attributes
- (None, 'accent-height'),
- (None, 'accumulate'),
- (None, 'additive'),
- (None, 'alphabetic'),
- (None, 'arabic-form'),
- (None, 'ascent'),
- (None, 'attributeName'),
- (None, 'attributeType'),
- (None, 'baseProfile'),
- (None, 'bbox'),
- (None, 'begin'),
- (None, 'by'),
- (None, 'calcMode'),
- (None, 'cap-height'),
- (None, 'class'),
- (None, 'clip-path'),
- (None, 'color'),
- (None, 'color-rendering'),
- (None, 'content'),
- (None, 'cx'),
- (None, 'cy'),
- (None, 'd'),
- (None, 'dx'),
- (None, 'dy'),
- (None, 'descent'),
- (None, 'display'),
- (None, 'dur'),
- (None, 'end'),
- (None, 'fill'),
- (None, 'fill-opacity'),
- (None, 'fill-rule'),
- (None, 'font-family'),
- (None, 'font-size'),
- (None, 'font-stretch'),
- (None, 'font-style'),
- (None, 'font-variant'),
- (None, 'font-weight'),
- (None, 'from'),
- (None, 'fx'),
- (None, 'fy'),
- (None, 'g1'),
- (None, 'g2'),
- (None, 'glyph-name'),
- (None, 'gradientUnits'),
- (None, 'hanging'),
- (None, 'height'),
- (None, 'horiz-adv-x'),
- (None, 'horiz-origin-x'),
- (None, 'id'),
- (None, 'ideographic'),
- (None, 'k'),
- (None, 'keyPoints'),
- (None, 'keySplines'),
- (None, 'keyTimes'),
- (None, 'lang'),
- (None, 'marker-end'),
- (None, 'marker-mid'),
- (None, 'marker-start'),
- (None, 'markerHeight'),
- (None, 'markerUnits'),
- (None, 'markerWidth'),
- (None, 'mathematical'),
- (None, 'max'),
- (None, 'min'),
- (None, 'name'),
- (None, 'offset'),
- (None, 'opacity'),
- (None, 'orient'),
- (None, 'origin'),
- (None, 'overline-position'),
- (None, 'overline-thickness'),
- (None, 'panose-1'),
- (None, 'path'),
- (None, 'pathLength'),
- (None, 'points'),
- (None, 'preserveAspectRatio'),
- (None, 'r'),
- (None, 'refX'),
- (None, 'refY'),
- (None, 'repeatCount'),
- (None, 'repeatDur'),
- (None, 'requiredExtensions'),
- (None, 'requiredFeatures'),
- (None, 'restart'),
- (None, 'rotate'),
- (None, 'rx'),
- (None, 'ry'),
- (None, 'slope'),
- (None, 'stemh'),
- (None, 'stemv'),
- (None, 'stop-color'),
- (None, 'stop-opacity'),
- (None, 'strikethrough-position'),
- (None, 'strikethrough-thickness'),
- (None, 'stroke'),
- (None, 'stroke-dasharray'),
- (None, 'stroke-dashoffset'),
- (None, 'stroke-linecap'),
- (None, 'stroke-linejoin'),
- (None, 'stroke-miterlimit'),
- (None, 'stroke-opacity'),
- (None, 'stroke-width'),
- (None, 'systemLanguage'),
- (None, 'target'),
- (None, 'text-anchor'),
- (None, 'to'),
- (None, 'transform'),
- (None, 'type'),
- (None, 'u1'),
- (None, 'u2'),
- (None, 'underline-position'),
- (None, 'underline-thickness'),
- (None, 'unicode'),
- (None, 'unicode-range'),
- (None, 'units-per-em'),
- (None, 'values'),
- (None, 'version'),
- (None, 'viewBox'),
- (None, 'visibility'),
- (None, 'width'),
- (None, 'widths'),
- (None, 'x'),
- (None, 'x-height'),
- (None, 'x1'),
- (None, 'x2'),
- (namespaces['xlink'], 'actuate'),
- (namespaces['xlink'], 'arcrole'),
- (namespaces['xlink'], 'href'),
- (namespaces['xlink'], 'role'),
- (namespaces['xlink'], 'show'),
- (namespaces['xlink'], 'title'),
- (namespaces['xlink'], 'type'),
- (namespaces['xml'], 'base'),
- (namespaces['xml'], 'lang'),
- (namespaces['xml'], 'space'),
- (None, 'y'),
- (None, 'y1'),
- (None, 'y2'),
- (None, 'zoomAndPan'),
-))
-
-attr_val_is_uri = frozenset((
- (None, 'href'),
- (None, 'src'),
- (None, 'cite'),
- (None, 'action'),
- (None, 'longdesc'),
- (None, 'poster'),
- (None, 'background'),
- (None, 'datasrc'),
- (None, 'dynsrc'),
- (None, 'lowsrc'),
- (None, 'ping'),
- (namespaces['xlink'], 'href'),
- (namespaces['xml'], 'base'),
-))
-
-svg_attr_val_allows_ref = frozenset((
- (None, 'clip-path'),
- (None, 'color-profile'),
- (None, 'cursor'),
- (None, 'fill'),
- (None, 'filter'),
- (None, 'marker'),
- (None, 'marker-start'),
- (None, 'marker-mid'),
- (None, 'marker-end'),
- (None, 'mask'),
- (None, 'stroke'),
-))
-
-svg_allow_local_href = frozenset((
- (None, 'altGlyph'),
- (None, 'animate'),
- (None, 'animateColor'),
- (None, 'animateMotion'),
- (None, 'animateTransform'),
- (None, 'cursor'),
- (None, 'feImage'),
- (None, 'filter'),
- (None, 'linearGradient'),
- (None, 'pattern'),
- (None, 'radialGradient'),
- (None, 'textpath'),
- (None, 'tref'),
- (None, 'set'),
- (None, 'use')
-))
-
-allowed_css_properties = frozenset((
- 'azimuth',
- 'background-color',
- 'border-bottom-color',
- 'border-collapse',
- 'border-color',
- 'border-left-color',
- 'border-right-color',
- 'border-top-color',
- 'clear',
- 'color',
- 'cursor',
- 'direction',
- 'display',
- 'elevation',
- 'float',
- 'font',
- 'font-family',
- 'font-size',
- 'font-style',
- 'font-variant',
- 'font-weight',
- 'height',
- 'letter-spacing',
- 'line-height',
- 'overflow',
- 'pause',
- 'pause-after',
- 'pause-before',
- 'pitch',
- 'pitch-range',
- 'richness',
- 'speak',
- 'speak-header',
- 'speak-numeral',
- 'speak-punctuation',
- 'speech-rate',
- 'stress',
- 'text-align',
- 'text-decoration',
- 'text-indent',
- 'unicode-bidi',
- 'vertical-align',
- 'voice-family',
- 'volume',
- 'white-space',
- 'width',
-))
-
-allowed_css_keywords = frozenset((
- 'auto',
- 'aqua',
- 'black',
- 'block',
- 'blue',
- 'bold',
- 'both',
- 'bottom',
- 'brown',
- 'center',
- 'collapse',
- 'dashed',
- 'dotted',
- 'fuchsia',
- 'gray',
- 'green',
- '!important',
- 'italic',
- 'left',
- 'lime',
- 'maroon',
- 'medium',
- 'none',
- 'navy',
- 'normal',
- 'nowrap',
- 'olive',
- 'pointer',
- 'purple',
- 'red',
- 'right',
- 'solid',
- 'silver',
- 'teal',
- 'top',
- 'transparent',
- 'underline',
- 'white',
- 'yellow',
-))
-
-allowed_svg_properties = frozenset((
- 'fill',
- 'fill-opacity',
- 'fill-rule',
- 'stroke',
- 'stroke-width',
- 'stroke-linecap',
- 'stroke-linejoin',
- 'stroke-opacity',
-))
-
-allowed_protocols = frozenset((
- 'ed2k',
- 'ftp',
- 'http',
- 'https',
- 'irc',
- 'mailto',
- 'news',
- 'gopher',
- 'nntp',
- 'telnet',
- 'webcal',
- 'xmpp',
- 'callto',
- 'feed',
- 'urn',
- 'aim',
- 'rsync',
- 'tag',
- 'ssh',
- 'sftp',
- 'rtsp',
- 'afs',
- 'data',
-))
-
-allowed_content_types = frozenset((
- 'image/png',
- 'image/jpeg',
- 'image/gif',
- 'image/webp',
- 'image/bmp',
- 'text/plain',
-))
-
-
-data_content_type = re.compile(r'''
- ^
- # Match a content type <application>/<type>
- (?P<content_type>[-a-zA-Z0-9.]+/[-a-zA-Z0-9.]+)
- # Match any character set and encoding
- (?:(?:;charset=(?:[-a-zA-Z0-9]+)(?:;(?:base64))?)
- |(?:;(?:base64))?(?:;charset=(?:[-a-zA-Z0-9]+))?)
- # Assume the rest is data
- ,.*
- $
- ''',
- re.VERBOSE)
-
-
-class Filter(base.Filter):
- """Sanitizes token stream of XHTML+MathML+SVG and of inline style attributes"""
- def __init__(self,
- source,
- allowed_elements=allowed_elements,
- allowed_attributes=allowed_attributes,
- allowed_css_properties=allowed_css_properties,
- allowed_css_keywords=allowed_css_keywords,
- allowed_svg_properties=allowed_svg_properties,
- allowed_protocols=allowed_protocols,
- allowed_content_types=allowed_content_types,
- attr_val_is_uri=attr_val_is_uri,
- svg_attr_val_allows_ref=svg_attr_val_allows_ref,
- svg_allow_local_href=svg_allow_local_href):
- """Creates a Filter
-
- :arg allowed_elements: set of elements to allow--everything else will
- be escaped
-
- :arg allowed_attributes: set of attributes to allow in
- elements--everything else will be stripped
-
- :arg allowed_css_properties: set of CSS properties to allow--everything
- else will be stripped
-
- :arg allowed_css_keywords: set of CSS keywords to allow--everything
- else will be stripped
-
- :arg allowed_svg_properties: set of SVG properties to allow--everything
- else will be removed
-
- :arg allowed_protocols: set of allowed protocols for URIs
-
- :arg allowed_content_types: set of allowed content types for ``data`` URIs.
-
- :arg attr_val_is_uri: set of attributes that have URI values--values
- that have a scheme not listed in ``allowed_protocols`` are removed
-
- :arg svg_attr_val_allows_ref: set of SVG attributes that can have
- references
-
- :arg svg_allow_local_href: set of SVG elements that can have local
- hrefs--these are removed
-
- """
- super(Filter, self).__init__(source)
-
- warnings.warn(_deprecation_msg, DeprecationWarning)
-
- self.allowed_elements = allowed_elements
- self.allowed_attributes = allowed_attributes
- self.allowed_css_properties = allowed_css_properties
- self.allowed_css_keywords = allowed_css_keywords
- self.allowed_svg_properties = allowed_svg_properties
- self.allowed_protocols = allowed_protocols
- self.allowed_content_types = allowed_content_types
- self.attr_val_is_uri = attr_val_is_uri
- self.svg_attr_val_allows_ref = svg_attr_val_allows_ref
- self.svg_allow_local_href = svg_allow_local_href
-
- def __iter__(self):
- for token in base.Filter.__iter__(self):
- token = self.sanitize_token(token)
- if token:
- yield token
-
- # Sanitize the +html+, escaping all elements not in ALLOWED_ELEMENTS, and
- # stripping out all attributes not in ALLOWED_ATTRIBUTES. Style attributes
- # are parsed, and a restricted set, specified by ALLOWED_CSS_PROPERTIES and
- # ALLOWED_CSS_KEYWORDS, are allowed through. attributes in ATTR_VAL_IS_URI
- # are scanned, and only URI schemes specified in ALLOWED_PROTOCOLS are
- # allowed.
- #
- # sanitize_html('<script> do_nasty_stuff() </script>')
- # => &lt;script> do_nasty_stuff() &lt;/script>
- # sanitize_html('<a href="javascript: sucker();">Click here for $100</a>')
- # => <a>Click here for $100</a>
- def sanitize_token(self, token):
-
- # accommodate filters which use token_type differently
- token_type = token["type"]
- if token_type in ("StartTag", "EndTag", "EmptyTag"):
- name = token["name"]
- namespace = token["namespace"]
- if ((namespace, name) in self.allowed_elements or
- (namespace is None and
- (namespaces["html"], name) in self.allowed_elements)):
- return self.allowed_token(token)
- else:
- return self.disallowed_token(token)
- elif token_type == "Comment":
- pass
- else:
- return token
-
- def allowed_token(self, token):
- if "data" in token:
- attrs = token["data"]
- attr_names = set(attrs.keys())
-
- # Remove forbidden attributes
- for to_remove in (attr_names - self.allowed_attributes):
- del token["data"][to_remove]
- attr_names.remove(to_remove)
-
- # Remove attributes with disallowed URL values
- for attr in (attr_names & self.attr_val_is_uri):
- assert attr in attrs
- # I don't have a clue where this regexp comes from or why it matches those
- # characters, nor why we call unescape. I just know it's always been here.
- # Should you be worried by this comment in a sanitizer? Yes. On the other hand, all
- # this will do is remove *more* than it otherwise would.
- val_unescaped = re.sub("[`\x00-\x20\x7f-\xa0\\s]+", '',
- unescape(attrs[attr])).lower()
- # remove replacement characters from unescaped characters
- val_unescaped = val_unescaped.replace("\ufffd", "")
- try:
- uri = urlparse.urlparse(val_unescaped)
- except ValueError:
- uri = None
- del attrs[attr]
- if uri and uri.scheme:
- if uri.scheme not in self.allowed_protocols:
- del attrs[attr]
- if uri.scheme == 'data':
- m = data_content_type.match(uri.path)
- if not m:
- del attrs[attr]
- elif m.group('content_type') not in self.allowed_content_types:
- del attrs[attr]
-
- for attr in self.svg_attr_val_allows_ref:
- if attr in attrs:
- attrs[attr] = re.sub(r'url\s*\(\s*[^#\s][^)]+?\)',
- ' ',
- unescape(attrs[attr]))
- if (token["name"] in self.svg_allow_local_href and
- (namespaces['xlink'], 'href') in attrs and re.search(r'^\s*[^#\s].*',
- attrs[(namespaces['xlink'], 'href')])):
- del attrs[(namespaces['xlink'], 'href')]
- if (None, 'style') in attrs:
- attrs[(None, 'style')] = self.sanitize_css(attrs[(None, 'style')])
- token["data"] = attrs
- return token
-
- def disallowed_token(self, token):
- token_type = token["type"]
- if token_type == "EndTag":
- token["data"] = "</%s>" % token["name"]
- elif token["data"]:
- assert token_type in ("StartTag", "EmptyTag")
- attrs = []
- for (ns, name), v in token["data"].items():
- attrs.append(' %s="%s"' % (name if ns is None else "%s:%s" % (prefixes[ns], name), escape(v)))
- token["data"] = "<%s%s>" % (token["name"], ''.join(attrs))
- else:
- token["data"] = "<%s>" % token["name"]
- if token.get("selfClosing"):
- token["data"] = token["data"][:-1] + "/>"
-
- token["type"] = "Characters"
-
- del token["name"]
- return token
-
- def sanitize_css(self, style):
- # disallow urls
- style = re.compile(r'url\s*\(\s*[^\s)]+?\s*\)\s*').sub(' ', style)
-
- # gauntlet
- if not re.match(r"""^([:,;#%.\sa-zA-Z0-9!]|\w-\w|'[\s\w]+'|"[\s\w]+"|\([\d,\s]+\))*$""", style):
- return ''
- if not re.match(r"^\s*([-\w]+\s*:[^:;]*(;\s*|$))*$", style):
- return ''
-
- clean = []
- for prop, value in re.findall(r"([-\w]+)\s*:\s*([^:;]*)", style):
- if not value:
- continue
- if prop.lower() in self.allowed_css_properties:
- clean.append(prop + ': ' + value + ';')
- elif prop.split('-')[0].lower() in ['background', 'border', 'margin',
- 'padding']:
- for keyword in value.split():
- if keyword not in self.allowed_css_keywords and \
- not re.match(r"^(#[0-9a-fA-F]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$", keyword): # noqa
- break
- else:
- clean.append(prop + ': ' + value + ';')
- elif prop.lower() in self.allowed_svg_properties:
- clean.append(prop + ': ' + value + ';')
-
- return ' '.join(clean)
diff --git a/src/pip/_vendor/html5lib/filters/whitespace.py b/src/pip/_vendor/html5lib/filters/whitespace.py
deleted file mode 100644
index 0d12584b4..000000000
--- a/src/pip/_vendor/html5lib/filters/whitespace.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-import re
-
-from . import base
-from ..constants import rcdataElements, spaceCharacters
-spaceCharacters = "".join(spaceCharacters)
-
-SPACES_REGEX = re.compile("[%s]+" % spaceCharacters)
-
-
-class Filter(base.Filter):
- """Collapses whitespace except in pre, textarea, and script elements"""
- spacePreserveElements = frozenset(["pre", "textarea"] + list(rcdataElements))
-
- def __iter__(self):
- preserve = 0
- for token in base.Filter.__iter__(self):
- type = token["type"]
- if type == "StartTag" \
- and (preserve or token["name"] in self.spacePreserveElements):
- preserve += 1
-
- elif type == "EndTag" and preserve:
- preserve -= 1
-
- elif not preserve and type == "SpaceCharacters" and token["data"]:
- # Test on token["data"] above to not introduce spaces where there were not
- token["data"] = " "
-
- elif not preserve and type == "Characters":
- token["data"] = collapse_spaces(token["data"])
-
- yield token
-
-
-def collapse_spaces(text):
- return SPACES_REGEX.sub(' ', text)
diff --git a/src/pip/_vendor/html5lib/html5parser.py b/src/pip/_vendor/html5lib/html5parser.py
deleted file mode 100644
index d06784f3d..000000000
--- a/src/pip/_vendor/html5lib/html5parser.py
+++ /dev/null
@@ -1,2795 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-from pip._vendor.six import with_metaclass, viewkeys
-
-import types
-
-from . import _inputstream
-from . import _tokenizer
-
-from . import treebuilders
-from .treebuilders.base import Marker
-
-from . import _utils
-from .constants import (
- spaceCharacters, asciiUpper2Lower,
- specialElements, headingElements, cdataElements, rcdataElements,
- tokenTypes, tagTokenTypes,
- namespaces,
- htmlIntegrationPointElements, mathmlTextIntegrationPointElements,
- adjustForeignAttributes as adjustForeignAttributesMap,
- adjustMathMLAttributes, adjustSVGAttributes,
- E,
- _ReparseException
-)
-
-
-def parse(doc, treebuilder="etree", namespaceHTMLElements=True, **kwargs):
- """Parse an HTML document as a string or file-like object into a tree
-
- :arg doc: the document to parse as a string or file-like object
-
- :arg treebuilder: the treebuilder to use when parsing
-
- :arg namespaceHTMLElements: whether or not to namespace HTML elements
-
- :returns: parsed tree
-
- Example:
-
- >>> from html5lib.html5parser import parse
- >>> parse('<html><body><p>This is a doc</p></body></html>')
- <Element u'{http://www.w3.org/1999/xhtml}html' at 0x7feac4909db0>
-
- """
- tb = treebuilders.getTreeBuilder(treebuilder)
- p = HTMLParser(tb, namespaceHTMLElements=namespaceHTMLElements)
- return p.parse(doc, **kwargs)
-
-
-def parseFragment(doc, container="div", treebuilder="etree", namespaceHTMLElements=True, **kwargs):
- """Parse an HTML fragment as a string or file-like object into a tree
-
- :arg doc: the fragment to parse as a string or file-like object
-
- :arg container: the container context to parse the fragment in
-
- :arg treebuilder: the treebuilder to use when parsing
-
- :arg namespaceHTMLElements: whether or not to namespace HTML elements
-
- :returns: parsed tree
-
- Example:
-
- >>> from html5lib.html5libparser import parseFragment
- >>> parseFragment('<b>this is a fragment</b>')
- <Element u'DOCUMENT_FRAGMENT' at 0x7feac484b090>
-
- """
- tb = treebuilders.getTreeBuilder(treebuilder)
- p = HTMLParser(tb, namespaceHTMLElements=namespaceHTMLElements)
- return p.parseFragment(doc, container=container, **kwargs)
-
-
-def method_decorator_metaclass(function):
- class Decorated(type):
- def __new__(meta, classname, bases, classDict):
- for attributeName, attribute in classDict.items():
- if isinstance(attribute, types.FunctionType):
- attribute = function(attribute)
-
- classDict[attributeName] = attribute
- return type.__new__(meta, classname, bases, classDict)
- return Decorated
-
-
-class HTMLParser(object):
- """HTML parser
-
- Generates a tree structure from a stream of (possibly malformed) HTML.
-
- """
-
- def __init__(self, tree=None, strict=False, namespaceHTMLElements=True, debug=False):
- """
- :arg tree: a treebuilder class controlling the type of tree that will be
- returned. Built in treebuilders can be accessed through
- html5lib.treebuilders.getTreeBuilder(treeType)
-
- :arg strict: raise an exception when a parse error is encountered
-
- :arg namespaceHTMLElements: whether or not to namespace HTML elements
-
- :arg debug: whether or not to enable debug mode which logs things
-
- Example:
-
- >>> from html5lib.html5parser import HTMLParser
- >>> parser = HTMLParser() # generates parser with etree builder
- >>> parser = HTMLParser('lxml', strict=True) # generates parser with lxml builder which is strict
-
- """
-
- # Raise an exception on the first error encountered
- self.strict = strict
-
- if tree is None:
- tree = treebuilders.getTreeBuilder("etree")
- self.tree = tree(namespaceHTMLElements)
- self.errors = []
-
- self.phases = {name: cls(self, self.tree) for name, cls in
- getPhases(debug).items()}
-
- def _parse(self, stream, innerHTML=False, container="div", scripting=False, **kwargs):
-
- self.innerHTMLMode = innerHTML
- self.container = container
- self.scripting = scripting
- self.tokenizer = _tokenizer.HTMLTokenizer(stream, parser=self, **kwargs)
- self.reset()
-
- try:
- self.mainLoop()
- except _ReparseException:
- self.reset()
- self.mainLoop()
-
- def reset(self):
- self.tree.reset()
- self.firstStartTag = False
- self.errors = []
- self.log = [] # only used with debug mode
- # "quirks" / "limited quirks" / "no quirks"
- self.compatMode = "no quirks"
-
- if self.innerHTMLMode:
- self.innerHTML = self.container.lower()
-
- if self.innerHTML in cdataElements:
- self.tokenizer.state = self.tokenizer.rcdataState
- elif self.innerHTML in rcdataElements:
- self.tokenizer.state = self.tokenizer.rawtextState
- elif self.innerHTML == 'plaintext':
- self.tokenizer.state = self.tokenizer.plaintextState
- else:
- # state already is data state
- # self.tokenizer.state = self.tokenizer.dataState
- pass
- self.phase = self.phases["beforeHtml"]
- self.phase.insertHtmlElement()
- self.resetInsertionMode()
- else:
- self.innerHTML = False # pylint:disable=redefined-variable-type
- self.phase = self.phases["initial"]
-
- self.lastPhase = None
-
- self.beforeRCDataPhase = None
-
- self.framesetOK = True
-
- @property
- def documentEncoding(self):
- """Name of the character encoding that was used to decode the input stream, or
- :obj:`None` if that is not determined yet
-
- """
- if not hasattr(self, 'tokenizer'):
- return None
- return self.tokenizer.stream.charEncoding[0].name
-
- def isHTMLIntegrationPoint(self, element):
- if (element.name == "annotation-xml" and
- element.namespace == namespaces["mathml"]):
- return ("encoding" in element.attributes and
- element.attributes["encoding"].translate(
- asciiUpper2Lower) in
- ("text/html", "application/xhtml+xml"))
- else:
- return (element.namespace, element.name) in htmlIntegrationPointElements
-
- def isMathMLTextIntegrationPoint(self, element):
- return (element.namespace, element.name) in mathmlTextIntegrationPointElements
-
- def mainLoop(self):
- CharactersToken = tokenTypes["Characters"]
- SpaceCharactersToken = tokenTypes["SpaceCharacters"]
- StartTagToken = tokenTypes["StartTag"]
- EndTagToken = tokenTypes["EndTag"]
- CommentToken = tokenTypes["Comment"]
- DoctypeToken = tokenTypes["Doctype"]
- ParseErrorToken = tokenTypes["ParseError"]
-
- for token in self.tokenizer:
- prev_token = None
- new_token = token
- while new_token is not None:
- prev_token = new_token
- currentNode = self.tree.openElements[-1] if self.tree.openElements else None
- currentNodeNamespace = currentNode.namespace if currentNode else None
- currentNodeName = currentNode.name if currentNode else None
-
- type = new_token["type"]
-
- if type == ParseErrorToken:
- self.parseError(new_token["data"], new_token.get("datavars", {}))
- new_token = None
- else:
- if (len(self.tree.openElements) == 0 or
- currentNodeNamespace == self.tree.defaultNamespace or
- (self.isMathMLTextIntegrationPoint(currentNode) and
- ((type == StartTagToken and
- token["name"] not in frozenset(["mglyph", "malignmark"])) or
- type in (CharactersToken, SpaceCharactersToken))) or
- (currentNodeNamespace == namespaces["mathml"] and
- currentNodeName == "annotation-xml" and
- type == StartTagToken and
- token["name"] == "svg") or
- (self.isHTMLIntegrationPoint(currentNode) and
- type in (StartTagToken, CharactersToken, SpaceCharactersToken))):
- phase = self.phase
- else:
- phase = self.phases["inForeignContent"]
-
- if type == CharactersToken:
- new_token = phase.processCharacters(new_token)
- elif type == SpaceCharactersToken:
- new_token = phase.processSpaceCharacters(new_token)
- elif type == StartTagToken:
- new_token = phase.processStartTag(new_token)
- elif type == EndTagToken:
- new_token = phase.processEndTag(new_token)
- elif type == CommentToken:
- new_token = phase.processComment(new_token)
- elif type == DoctypeToken:
- new_token = phase.processDoctype(new_token)
-
- if (type == StartTagToken and prev_token["selfClosing"] and
- not prev_token["selfClosingAcknowledged"]):
- self.parseError("non-void-element-with-trailing-solidus",
- {"name": prev_token["name"]})
-
- # When the loop finishes it's EOF
- reprocess = True
- phases = []
- while reprocess:
- phases.append(self.phase)
- reprocess = self.phase.processEOF()
- if reprocess:
- assert self.phase not in phases
-
- def parse(self, stream, *args, **kwargs):
- """Parse a HTML document into a well-formed tree
-
- :arg stream: a file-like object or string containing the HTML to be parsed
-
- The optional encoding parameter must be a string that indicates
- the encoding. If specified, that encoding will be used,
- regardless of any BOM or later declaration (such as in a meta
- element).
-
- :arg scripting: treat noscript elements as if JavaScript was turned on
-
- :returns: parsed tree
-
- Example:
-
- >>> from html5lib.html5parser import HTMLParser
- >>> parser = HTMLParser()
- >>> parser.parse('<html><body><p>This is a doc</p></body></html>')
- <Element u'{http://www.w3.org/1999/xhtml}html' at 0x7feac4909db0>
-
- """
- self._parse(stream, False, None, *args, **kwargs)
- return self.tree.getDocument()
-
- def parseFragment(self, stream, *args, **kwargs):
- """Parse a HTML fragment into a well-formed tree fragment
-
- :arg container: name of the element we're setting the innerHTML
- property if set to None, default to 'div'
-
- :arg stream: a file-like object or string containing the HTML to be parsed
-
- The optional encoding parameter must be a string that indicates
- the encoding. If specified, that encoding will be used,
- regardless of any BOM or later declaration (such as in a meta
- element)
-
- :arg scripting: treat noscript elements as if JavaScript was turned on
-
- :returns: parsed tree
-
- Example:
-
- >>> from html5lib.html5libparser import HTMLParser
- >>> parser = HTMLParser()
- >>> parser.parseFragment('<b>this is a fragment</b>')
- <Element u'DOCUMENT_FRAGMENT' at 0x7feac484b090>
-
- """
- self._parse(stream, True, *args, **kwargs)
- return self.tree.getFragment()
-
- def parseError(self, errorcode="XXX-undefined-error", datavars=None):
- # XXX The idea is to make errorcode mandatory.
- if datavars is None:
- datavars = {}
- self.errors.append((self.tokenizer.stream.position(), errorcode, datavars))
- if self.strict:
- raise ParseError(E[errorcode] % datavars)
-
- def adjustMathMLAttributes(self, token):
- adjust_attributes(token, adjustMathMLAttributes)
-
- def adjustSVGAttributes(self, token):
- adjust_attributes(token, adjustSVGAttributes)
-
- def adjustForeignAttributes(self, token):
- adjust_attributes(token, adjustForeignAttributesMap)
-
- def reparseTokenNormal(self, token):
- # pylint:disable=unused-argument
- self.parser.phase()
-
- def resetInsertionMode(self):
- # The name of this method is mostly historical. (It's also used in the
- # specification.)
- last = False
- newModes = {
- "select": "inSelect",
- "td": "inCell",
- "th": "inCell",
- "tr": "inRow",
- "tbody": "inTableBody",
- "thead": "inTableBody",
- "tfoot": "inTableBody",
- "caption": "inCaption",
- "colgroup": "inColumnGroup",
- "table": "inTable",
- "head": "inBody",
- "body": "inBody",
- "frameset": "inFrameset",
- "html": "beforeHead"
- }
- for node in self.tree.openElements[::-1]:
- nodeName = node.name
- new_phase = None
- if node == self.tree.openElements[0]:
- assert self.innerHTML
- last = True
- nodeName = self.innerHTML
- # Check for conditions that should only happen in the innerHTML
- # case
- if nodeName in ("select", "colgroup", "head", "html"):
- assert self.innerHTML
-
- if not last and node.namespace != self.tree.defaultNamespace:
- continue
-
- if nodeName in newModes:
- new_phase = self.phases[newModes[nodeName]]
- break
- elif last:
- new_phase = self.phases["inBody"]
- break
-
- self.phase = new_phase
-
- def parseRCDataRawtext(self, token, contentType):
- # Generic RCDATA/RAWTEXT Parsing algorithm
- assert contentType in ("RAWTEXT", "RCDATA")
-
- self.tree.insertElement(token)
-
- if contentType == "RAWTEXT":
- self.tokenizer.state = self.tokenizer.rawtextState
- else:
- self.tokenizer.state = self.tokenizer.rcdataState
-
- self.originalPhase = self.phase
-
- self.phase = self.phases["text"]
-
-
-@_utils.memoize
-def getPhases(debug):
- def log(function):
- """Logger that records which phase processes each token"""
- type_names = {value: key for key, value in tokenTypes.items()}
-
- def wrapped(self, *args, **kwargs):
- if function.__name__.startswith("process") and len(args) > 0:
- token = args[0]
- info = {"type": type_names[token['type']]}
- if token['type'] in tagTokenTypes:
- info["name"] = token['name']
-
- self.parser.log.append((self.parser.tokenizer.state.__name__,
- self.parser.phase.__class__.__name__,
- self.__class__.__name__,
- function.__name__,
- info))
- return function(self, *args, **kwargs)
- else:
- return function(self, *args, **kwargs)
- return wrapped
-
- def getMetaclass(use_metaclass, metaclass_func):
- if use_metaclass:
- return method_decorator_metaclass(metaclass_func)
- else:
- return type
-
- # pylint:disable=unused-argument
- class Phase(with_metaclass(getMetaclass(debug, log))):
- """Base class for helper object that implements each phase of processing
- """
- __slots__ = ("parser", "tree", "__startTagCache", "__endTagCache")
-
- def __init__(self, parser, tree):
- self.parser = parser
- self.tree = tree
- self.__startTagCache = {}
- self.__endTagCache = {}
-
- def processEOF(self):
- raise NotImplementedError
-
- def processComment(self, token):
- # For most phases the following is correct. Where it's not it will be
- # overridden.
- self.tree.insertComment(token, self.tree.openElements[-1])
-
- def processDoctype(self, token):
- self.parser.parseError("unexpected-doctype")
-
- def processCharacters(self, token):
- self.tree.insertText(token["data"])
-
- def processSpaceCharacters(self, token):
- self.tree.insertText(token["data"])
-
- def processStartTag(self, token):
- # Note the caching is done here rather than BoundMethodDispatcher as doing it there
- # requires a circular reference to the Phase, and this ends up with a significant
- # (CPython 2.7, 3.8) GC cost when parsing many short inputs
- name = token["name"]
- # In Py2, using `in` is quicker in general than try/except KeyError
- # In Py3, `in` is quicker when there are few cache hits (typically short inputs)
- if name in self.__startTagCache:
- func = self.__startTagCache[name]
- else:
- func = self.__startTagCache[name] = self.startTagHandler[name]
- # bound the cache size in case we get loads of unknown tags
- while len(self.__startTagCache) > len(self.startTagHandler) * 1.1:
- # this makes the eviction policy random on Py < 3.7 and FIFO >= 3.7
- self.__startTagCache.pop(next(iter(self.__startTagCache)))
- return func(token)
-
- def startTagHtml(self, token):
- if not self.parser.firstStartTag and token["name"] == "html":
- self.parser.parseError("non-html-root")
- # XXX Need a check here to see if the first start tag token emitted is
- # this token... If it's not, invoke self.parser.parseError().
- for attr, value in token["data"].items():
- if attr not in self.tree.openElements[0].attributes:
- self.tree.openElements[0].attributes[attr] = value
- self.parser.firstStartTag = False
-
- def processEndTag(self, token):
- # Note the caching is done here rather than BoundMethodDispatcher as doing it there
- # requires a circular reference to the Phase, and this ends up with a significant
- # (CPython 2.7, 3.8) GC cost when parsing many short inputs
- name = token["name"]
- # In Py2, using `in` is quicker in general than try/except KeyError
- # In Py3, `in` is quicker when there are few cache hits (typically short inputs)
- if name in self.__endTagCache:
- func = self.__endTagCache[name]
- else:
- func = self.__endTagCache[name] = self.endTagHandler[name]
- # bound the cache size in case we get loads of unknown tags
- while len(self.__endTagCache) > len(self.endTagHandler) * 1.1:
- # this makes the eviction policy random on Py < 3.7 and FIFO >= 3.7
- self.__endTagCache.pop(next(iter(self.__endTagCache)))
- return func(token)
-
- class InitialPhase(Phase):
- __slots__ = tuple()
-
- def processSpaceCharacters(self, token):
- pass
-
- def processComment(self, token):
- self.tree.insertComment(token, self.tree.document)
-
- def processDoctype(self, token):
- name = token["name"]
- publicId = token["publicId"]
- systemId = token["systemId"]
- correct = token["correct"]
-
- if (name != "html" or publicId is not None or
- systemId is not None and systemId != "about:legacy-compat"):
- self.parser.parseError("unknown-doctype")
-
- if publicId is None:
- publicId = ""
-
- self.tree.insertDoctype(token)
-
- if publicId != "":
- publicId = publicId.translate(asciiUpper2Lower)
-
- if (not correct or token["name"] != "html" or
- publicId.startswith(
- ("+//silmaril//dtd html pro v0r11 19970101//",
- "-//advasoft ltd//dtd html 3.0 aswedit + extensions//",
- "-//as//dtd html 3.0 aswedit + extensions//",
- "-//ietf//dtd html 2.0 level 1//",
- "-//ietf//dtd html 2.0 level 2//",
- "-//ietf//dtd html 2.0 strict level 1//",
- "-//ietf//dtd html 2.0 strict level 2//",
- "-//ietf//dtd html 2.0 strict//",
- "-//ietf//dtd html 2.0//",
- "-//ietf//dtd html 2.1e//",
- "-//ietf//dtd html 3.0//",
- "-//ietf//dtd html 3.2 final//",
- "-//ietf//dtd html 3.2//",
- "-//ietf//dtd html 3//",
- "-//ietf//dtd html level 0//",
- "-//ietf//dtd html level 1//",
- "-//ietf//dtd html level 2//",
- "-//ietf//dtd html level 3//",
- "-//ietf//dtd html strict level 0//",
- "-//ietf//dtd html strict level 1//",
- "-//ietf//dtd html strict level 2//",
- "-//ietf//dtd html strict level 3//",
- "-//ietf//dtd html strict//",
- "-//ietf//dtd html//",
- "-//metrius//dtd metrius presentational//",
- "-//microsoft//dtd internet explorer 2.0 html strict//",
- "-//microsoft//dtd internet explorer 2.0 html//",
- "-//microsoft//dtd internet explorer 2.0 tables//",
- "-//microsoft//dtd internet explorer 3.0 html strict//",
- "-//microsoft//dtd internet explorer 3.0 html//",
- "-//microsoft//dtd internet explorer 3.0 tables//",
- "-//netscape comm. corp.//dtd html//",
- "-//netscape comm. corp.//dtd strict html//",
- "-//o'reilly and associates//dtd html 2.0//",
- "-//o'reilly and associates//dtd html extended 1.0//",
- "-//o'reilly and associates//dtd html extended relaxed 1.0//",
- "-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//",
- "-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//",
- "-//spyglass//dtd html 2.0 extended//",
- "-//sq//dtd html 2.0 hotmetal + extensions//",
- "-//sun microsystems corp.//dtd hotjava html//",
- "-//sun microsystems corp.//dtd hotjava strict html//",
- "-//w3c//dtd html 3 1995-03-24//",
- "-//w3c//dtd html 3.2 draft//",
- "-//w3c//dtd html 3.2 final//",
- "-//w3c//dtd html 3.2//",
- "-//w3c//dtd html 3.2s draft//",
- "-//w3c//dtd html 4.0 frameset//",
- "-//w3c//dtd html 4.0 transitional//",
- "-//w3c//dtd html experimental 19960712//",
- "-//w3c//dtd html experimental 970421//",
- "-//w3c//dtd w3 html//",
- "-//w3o//dtd w3 html 3.0//",
- "-//webtechs//dtd mozilla html 2.0//",
- "-//webtechs//dtd mozilla html//")) or
- publicId in ("-//w3o//dtd w3 html strict 3.0//en//",
- "-/w3c/dtd html 4.0 transitional/en",
- "html") or
- publicId.startswith(
- ("-//w3c//dtd html 4.01 frameset//",
- "-//w3c//dtd html 4.01 transitional//")) and
- systemId is None or
- systemId and systemId.lower() == "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd"):
- self.parser.compatMode = "quirks"
- elif (publicId.startswith(
- ("-//w3c//dtd xhtml 1.0 frameset//",
- "-//w3c//dtd xhtml 1.0 transitional//")) or
- publicId.startswith(
- ("-//w3c//dtd html 4.01 frameset//",
- "-//w3c//dtd html 4.01 transitional//")) and
- systemId is not None):
- self.parser.compatMode = "limited quirks"
-
- self.parser.phase = self.parser.phases["beforeHtml"]
-
- def anythingElse(self):
- self.parser.compatMode = "quirks"
- self.parser.phase = self.parser.phases["beforeHtml"]
-
- def processCharacters(self, token):
- self.parser.parseError("expected-doctype-but-got-chars")
- self.anythingElse()
- return token
-
- def processStartTag(self, token):
- self.parser.parseError("expected-doctype-but-got-start-tag",
- {"name": token["name"]})
- self.anythingElse()
- return token
-
- def processEndTag(self, token):
- self.parser.parseError("expected-doctype-but-got-end-tag",
- {"name": token["name"]})
- self.anythingElse()
- return token
-
- def processEOF(self):
- self.parser.parseError("expected-doctype-but-got-eof")
- self.anythingElse()
- return True
-
- class BeforeHtmlPhase(Phase):
- __slots__ = tuple()
-
- # helper methods
- def insertHtmlElement(self):
- self.tree.insertRoot(impliedTagToken("html", "StartTag"))
- self.parser.phase = self.parser.phases["beforeHead"]
-
- # other
- def processEOF(self):
- self.insertHtmlElement()
- return True
-
- def processComment(self, token):
- self.tree.insertComment(token, self.tree.document)
-
- def processSpaceCharacters(self, token):
- pass
-
- def processCharacters(self, token):
- self.insertHtmlElement()
- return token
-
- def processStartTag(self, token):
- if token["name"] == "html":
- self.parser.firstStartTag = True
- self.insertHtmlElement()
- return token
-
- def processEndTag(self, token):
- if token["name"] not in ("head", "body", "html", "br"):
- self.parser.parseError("unexpected-end-tag-before-html",
- {"name": token["name"]})
- else:
- self.insertHtmlElement()
- return token
-
- class BeforeHeadPhase(Phase):
- __slots__ = tuple()
-
- def processEOF(self):
- self.startTagHead(impliedTagToken("head", "StartTag"))
- return True
-
- def processSpaceCharacters(self, token):
- pass
-
- def processCharacters(self, token):
- self.startTagHead(impliedTagToken("head", "StartTag"))
- return token
-
- def startTagHtml(self, token):
- return self.parser.phases["inBody"].processStartTag(token)
-
- def startTagHead(self, token):
- self.tree.insertElement(token)
- self.tree.headPointer = self.tree.openElements[-1]
- self.parser.phase = self.parser.phases["inHead"]
-
- def startTagOther(self, token):
- self.startTagHead(impliedTagToken("head", "StartTag"))
- return token
-
- def endTagImplyHead(self, token):
- self.startTagHead(impliedTagToken("head", "StartTag"))
- return token
-
- def endTagOther(self, token):
- self.parser.parseError("end-tag-after-implied-root",
- {"name": token["name"]})
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", startTagHtml),
- ("head", startTagHead)
- ])
- startTagHandler.default = startTagOther
-
- endTagHandler = _utils.MethodDispatcher([
- (("head", "body", "html", "br"), endTagImplyHead)
- ])
- endTagHandler.default = endTagOther
-
- class InHeadPhase(Phase):
- __slots__ = tuple()
-
- # the real thing
- def processEOF(self):
- self.anythingElse()
- return True
-
- def processCharacters(self, token):
- self.anythingElse()
- return token
-
- def startTagHtml(self, token):
- return self.parser.phases["inBody"].processStartTag(token)
-
- def startTagHead(self, token):
- self.parser.parseError("two-heads-are-not-better-than-one")
-
- def startTagBaseLinkCommand(self, token):
- self.tree.insertElement(token)
- self.tree.openElements.pop()
- token["selfClosingAcknowledged"] = True
-
- def startTagMeta(self, token):
- self.tree.insertElement(token)
- self.tree.openElements.pop()
- token["selfClosingAcknowledged"] = True
-
- attributes = token["data"]
- if self.parser.tokenizer.stream.charEncoding[1] == "tentative":
- if "charset" in attributes:
- self.parser.tokenizer.stream.changeEncoding(attributes["charset"])
- elif ("content" in attributes and
- "http-equiv" in attributes and
- attributes["http-equiv"].lower() == "content-type"):
- # Encoding it as UTF-8 here is a hack, as really we should pass
- # the abstract Unicode string, and just use the
- # ContentAttrParser on that, but using UTF-8 allows all chars
- # to be encoded and as a ASCII-superset works.
- data = _inputstream.EncodingBytes(attributes["content"].encode("utf-8"))
- parser = _inputstream.ContentAttrParser(data)
- codec = parser.parse()
- self.parser.tokenizer.stream.changeEncoding(codec)
-
- def startTagTitle(self, token):
- self.parser.parseRCDataRawtext(token, "RCDATA")
-
- def startTagNoFramesStyle(self, token):
- # Need to decide whether to implement the scripting-disabled case
- self.parser.parseRCDataRawtext(token, "RAWTEXT")
-
- def startTagNoscript(self, token):
- if self.parser.scripting:
- self.parser.parseRCDataRawtext(token, "RAWTEXT")
- else:
- self.tree.insertElement(token)
- self.parser.phase = self.parser.phases["inHeadNoscript"]
-
- def startTagScript(self, token):
- self.tree.insertElement(token)
- self.parser.tokenizer.state = self.parser.tokenizer.scriptDataState
- self.parser.originalPhase = self.parser.phase
- self.parser.phase = self.parser.phases["text"]
-
- def startTagOther(self, token):
- self.anythingElse()
- return token
-
- def endTagHead(self, token):
- node = self.parser.tree.openElements.pop()
- assert node.name == "head", "Expected head got %s" % node.name
- self.parser.phase = self.parser.phases["afterHead"]
-
- def endTagHtmlBodyBr(self, token):
- self.anythingElse()
- return token
-
- def endTagOther(self, token):
- self.parser.parseError("unexpected-end-tag", {"name": token["name"]})
-
- def anythingElse(self):
- self.endTagHead(impliedTagToken("head"))
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", startTagHtml),
- ("title", startTagTitle),
- (("noframes", "style"), startTagNoFramesStyle),
- ("noscript", startTagNoscript),
- ("script", startTagScript),
- (("base", "basefont", "bgsound", "command", "link"),
- startTagBaseLinkCommand),
- ("meta", startTagMeta),
- ("head", startTagHead)
- ])
- startTagHandler.default = startTagOther
-
- endTagHandler = _utils.MethodDispatcher([
- ("head", endTagHead),
- (("br", "html", "body"), endTagHtmlBodyBr)
- ])
- endTagHandler.default = endTagOther
-
- class InHeadNoscriptPhase(Phase):
- __slots__ = tuple()
-
- def processEOF(self):
- self.parser.parseError("eof-in-head-noscript")
- self.anythingElse()
- return True
-
- def processComment(self, token):
- return self.parser.phases["inHead"].processComment(token)
-
- def processCharacters(self, token):
- self.parser.parseError("char-in-head-noscript")
- self.anythingElse()
- return token
-
- def processSpaceCharacters(self, token):
- return self.parser.phases["inHead"].processSpaceCharacters(token)
-
- def startTagHtml(self, token):
- return self.parser.phases["inBody"].processStartTag(token)
-
- def startTagBaseLinkCommand(self, token):
- return self.parser.phases["inHead"].processStartTag(token)
-
- def startTagHeadNoscript(self, token):
- self.parser.parseError("unexpected-start-tag", {"name": token["name"]})
-
- def startTagOther(self, token):
- self.parser.parseError("unexpected-inhead-noscript-tag", {"name": token["name"]})
- self.anythingElse()
- return token
-
- def endTagNoscript(self, token):
- node = self.parser.tree.openElements.pop()
- assert node.name == "noscript", "Expected noscript got %s" % node.name
- self.parser.phase = self.parser.phases["inHead"]
-
- def endTagBr(self, token):
- self.parser.parseError("unexpected-inhead-noscript-tag", {"name": token["name"]})
- self.anythingElse()
- return token
-
- def endTagOther(self, token):
- self.parser.parseError("unexpected-end-tag", {"name": token["name"]})
-
- def anythingElse(self):
- # Caller must raise parse error first!
- self.endTagNoscript(impliedTagToken("noscript"))
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", startTagHtml),
- (("basefont", "bgsound", "link", "meta", "noframes", "style"), startTagBaseLinkCommand),
- (("head", "noscript"), startTagHeadNoscript),
- ])
- startTagHandler.default = startTagOther
-
- endTagHandler = _utils.MethodDispatcher([
- ("noscript", endTagNoscript),
- ("br", endTagBr),
- ])
- endTagHandler.default = endTagOther
-
- class AfterHeadPhase(Phase):
- __slots__ = tuple()
-
- def processEOF(self):
- self.anythingElse()
- return True
-
- def processCharacters(self, token):
- self.anythingElse()
- return token
-
- def startTagHtml(self, token):
- return self.parser.phases["inBody"].processStartTag(token)
-
- def startTagBody(self, token):
- self.parser.framesetOK = False
- self.tree.insertElement(token)
- self.parser.phase = self.parser.phases["inBody"]
-
- def startTagFrameset(self, token):
- self.tree.insertElement(token)
- self.parser.phase = self.parser.phases["inFrameset"]
-
- def startTagFromHead(self, token):
- self.parser.parseError("unexpected-start-tag-out-of-my-head",
- {"name": token["name"]})
- self.tree.openElements.append(self.tree.headPointer)
- self.parser.phases["inHead"].processStartTag(token)
- for node in self.tree.openElements[::-1]:
- if node.name == "head":
- self.tree.openElements.remove(node)
- break
-
- def startTagHead(self, token):
- self.parser.parseError("unexpected-start-tag", {"name": token["name"]})
-
- def startTagOther(self, token):
- self.anythingElse()
- return token
-
- def endTagHtmlBodyBr(self, token):
- self.anythingElse()
- return token
-
- def endTagOther(self, token):
- self.parser.parseError("unexpected-end-tag", {"name": token["name"]})
-
- def anythingElse(self):
- self.tree.insertElement(impliedTagToken("body", "StartTag"))
- self.parser.phase = self.parser.phases["inBody"]
- self.parser.framesetOK = True
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", startTagHtml),
- ("body", startTagBody),
- ("frameset", startTagFrameset),
- (("base", "basefont", "bgsound", "link", "meta", "noframes", "script",
- "style", "title"),
- startTagFromHead),
- ("head", startTagHead)
- ])
- startTagHandler.default = startTagOther
- endTagHandler = _utils.MethodDispatcher([(("body", "html", "br"),
- endTagHtmlBodyBr)])
- endTagHandler.default = endTagOther
-
- class InBodyPhase(Phase):
- # http://www.whatwg.org/specs/web-apps/current-work/#parsing-main-inbody
- # the really-really-really-very crazy mode
- __slots__ = ("processSpaceCharacters",)
-
- def __init__(self, *args, **kwargs):
- super(InBodyPhase, self).__init__(*args, **kwargs)
- # Set this to the default handler
- self.processSpaceCharacters = self.processSpaceCharactersNonPre
-
- def isMatchingFormattingElement(self, node1, node2):
- return (node1.name == node2.name and
- node1.namespace == node2.namespace and
- node1.attributes == node2.attributes)
-
- # helper
- def addFormattingElement(self, token):
- self.tree.insertElement(token)
- element = self.tree.openElements[-1]
-
- matchingElements = []
- for node in self.tree.activeFormattingElements[::-1]:
- if node is Marker:
- break
- elif self.isMatchingFormattingElement(node, element):
- matchingElements.append(node)
-
- assert len(matchingElements) <= 3
- if len(matchingElements) == 3:
- self.tree.activeFormattingElements.remove(matchingElements[-1])
- self.tree.activeFormattingElements.append(element)
-
- # the real deal
- def processEOF(self):
- allowed_elements = frozenset(("dd", "dt", "li", "p", "tbody", "td",
- "tfoot", "th", "thead", "tr", "body",
- "html"))
- for node in self.tree.openElements[::-1]:
- if node.name not in allowed_elements:
- self.parser.parseError("expected-closing-tag-but-got-eof")
- break
- # Stop parsing
-
- def processSpaceCharactersDropNewline(self, token):
- # Sometimes (start of <pre>, <listing>, and <textarea> blocks) we
- # want to drop leading newlines
- data = token["data"]
- self.processSpaceCharacters = self.processSpaceCharactersNonPre
- if (data.startswith("\n") and
- self.tree.openElements[-1].name in ("pre", "listing", "textarea") and
- not self.tree.openElements[-1].hasContent()):
- data = data[1:]
- if data:
- self.tree.reconstructActiveFormattingElements()
- self.tree.insertText(data)
-
- def processCharacters(self, token):
- if token["data"] == "\u0000":
- # The tokenizer should always emit null on its own
- return
- self.tree.reconstructActiveFormattingElements()
- self.tree.insertText(token["data"])
- # This must be bad for performance
- if (self.parser.framesetOK and
- any([char not in spaceCharacters
- for char in token["data"]])):
- self.parser.framesetOK = False
-
- def processSpaceCharactersNonPre(self, token):
- self.tree.reconstructActiveFormattingElements()
- self.tree.insertText(token["data"])
-
- def startTagProcessInHead(self, token):
- return self.parser.phases["inHead"].processStartTag(token)
-
- def startTagBody(self, token):
- self.parser.parseError("unexpected-start-tag", {"name": "body"})
- if (len(self.tree.openElements) == 1 or
- self.tree.openElements[1].name != "body"):
- assert self.parser.innerHTML
- else:
- self.parser.framesetOK = False
- for attr, value in token["data"].items():
- if attr not in self.tree.openElements[1].attributes:
- self.tree.openElements[1].attributes[attr] = value
-
- def startTagFrameset(self, token):
- self.parser.parseError("unexpected-start-tag", {"name": "frameset"})
- if (len(self.tree.openElements) == 1 or self.tree.openElements[1].name != "body"):
- assert self.parser.innerHTML
- elif not self.parser.framesetOK:
- pass
- else:
- if self.tree.openElements[1].parent:
- self.tree.openElements[1].parent.removeChild(self.tree.openElements[1])
- while self.tree.openElements[-1].name != "html":
- self.tree.openElements.pop()
- self.tree.insertElement(token)
- self.parser.phase = self.parser.phases["inFrameset"]
-
- def startTagCloseP(self, token):
- if self.tree.elementInScope("p", variant="button"):
- self.endTagP(impliedTagToken("p"))
- self.tree.insertElement(token)
-
- def startTagPreListing(self, token):
- if self.tree.elementInScope("p", variant="button"):
- self.endTagP(impliedTagToken("p"))
- self.tree.insertElement(token)
- self.parser.framesetOK = False
- self.processSpaceCharacters = self.processSpaceCharactersDropNewline
-
- def startTagForm(self, token):
- if self.tree.formPointer:
- self.parser.parseError("unexpected-start-tag", {"name": "form"})
- else:
- if self.tree.elementInScope("p", variant="button"):
- self.endTagP(impliedTagToken("p"))
- self.tree.insertElement(token)
- self.tree.formPointer = self.tree.openElements[-1]
-
- def startTagListItem(self, token):
- self.parser.framesetOK = False
-
- stopNamesMap = {"li": ["li"],
- "dt": ["dt", "dd"],
- "dd": ["dt", "dd"]}
- stopNames = stopNamesMap[token["name"]]
- for node in reversed(self.tree.openElements):
- if node.name in stopNames:
- self.parser.phase.processEndTag(
- impliedTagToken(node.name, "EndTag"))
- break
- if (node.nameTuple in specialElements and
- node.name not in ("address", "div", "p")):
- break
-
- if self.tree.elementInScope("p", variant="button"):
- self.parser.phase.processEndTag(
- impliedTagToken("p", "EndTag"))
-
- self.tree.insertElement(token)
-
- def startTagPlaintext(self, token):
- if self.tree.elementInScope("p", variant="button"):
- self.endTagP(impliedTagToken("p"))
- self.tree.insertElement(token)
- self.parser.tokenizer.state = self.parser.tokenizer.plaintextState
-
- def startTagHeading(self, token):
- if self.tree.elementInScope("p", variant="button"):
- self.endTagP(impliedTagToken("p"))
- if self.tree.openElements[-1].name in headingElements:
- self.parser.parseError("unexpected-start-tag", {"name": token["name"]})
- self.tree.openElements.pop()
- self.tree.insertElement(token)
-
- def startTagA(self, token):
- afeAElement = self.tree.elementInActiveFormattingElements("a")
- if afeAElement:
- self.parser.parseError("unexpected-start-tag-implies-end-tag",
- {"startName": "a", "endName": "a"})
- self.endTagFormatting(impliedTagToken("a"))
- if afeAElement in self.tree.openElements:
- self.tree.openElements.remove(afeAElement)
- if afeAElement in self.tree.activeFormattingElements:
- self.tree.activeFormattingElements.remove(afeAElement)
- self.tree.reconstructActiveFormattingElements()
- self.addFormattingElement(token)
-
- def startTagFormatting(self, token):
- self.tree.reconstructActiveFormattingElements()
- self.addFormattingElement(token)
-
- def startTagNobr(self, token):
- self.tree.reconstructActiveFormattingElements()
- if self.tree.elementInScope("nobr"):
- self.parser.parseError("unexpected-start-tag-implies-end-tag",
- {"startName": "nobr", "endName": "nobr"})
- self.processEndTag(impliedTagToken("nobr"))
- # XXX Need tests that trigger the following
- self.tree.reconstructActiveFormattingElements()
- self.addFormattingElement(token)
-
- def startTagButton(self, token):
- if self.tree.elementInScope("button"):
- self.parser.parseError("unexpected-start-tag-implies-end-tag",
- {"startName": "button", "endName": "button"})
- self.processEndTag(impliedTagToken("button"))
- return token
- else:
- self.tree.reconstructActiveFormattingElements()
- self.tree.insertElement(token)
- self.parser.framesetOK = False
-
- def startTagAppletMarqueeObject(self, token):
- self.tree.reconstructActiveFormattingElements()
- self.tree.insertElement(token)
- self.tree.activeFormattingElements.append(Marker)
- self.parser.framesetOK = False
-
- def startTagXmp(self, token):
- if self.tree.elementInScope("p", variant="button"):
- self.endTagP(impliedTagToken("p"))
- self.tree.reconstructActiveFormattingElements()
- self.parser.framesetOK = False
- self.parser.parseRCDataRawtext(token, "RAWTEXT")
-
- def startTagTable(self, token):
- if self.parser.compatMode != "quirks":
- if self.tree.elementInScope("p", variant="button"):
- self.processEndTag(impliedTagToken("p"))
- self.tree.insertElement(token)
- self.parser.framesetOK = False
- self.parser.phase = self.parser.phases["inTable"]
-
- def startTagVoidFormatting(self, token):
- self.tree.reconstructActiveFormattingElements()
- self.tree.insertElement(token)
- self.tree.openElements.pop()
- token["selfClosingAcknowledged"] = True
- self.parser.framesetOK = False
-
- def startTagInput(self, token):
- framesetOK = self.parser.framesetOK
- self.startTagVoidFormatting(token)
- if ("type" in token["data"] and
- token["data"]["type"].translate(asciiUpper2Lower) == "hidden"):
- # input type=hidden doesn't change framesetOK
- self.parser.framesetOK = framesetOK
-
- def startTagParamSource(self, token):
- self.tree.insertElement(token)
- self.tree.openElements.pop()
- token["selfClosingAcknowledged"] = True
-
- def startTagHr(self, token):
- if self.tree.elementInScope("p", variant="button"):
- self.endTagP(impliedTagToken("p"))
- self.tree.insertElement(token)
- self.tree.openElements.pop()
- token["selfClosingAcknowledged"] = True
- self.parser.framesetOK = False
-
- def startTagImage(self, token):
- # No really...
- self.parser.parseError("unexpected-start-tag-treated-as",
- {"originalName": "image", "newName": "img"})
- self.processStartTag(impliedTagToken("img", "StartTag",
- attributes=token["data"],
- selfClosing=token["selfClosing"]))
-
- def startTagIsIndex(self, token):
- self.parser.parseError("deprecated-tag", {"name": "isindex"})
- if self.tree.formPointer:
- return
- form_attrs = {}
- if "action" in token["data"]:
- form_attrs["action"] = token["data"]["action"]
- self.processStartTag(impliedTagToken("form", "StartTag",
- attributes=form_attrs))
- self.processStartTag(impliedTagToken("hr", "StartTag"))
- self.processStartTag(impliedTagToken("label", "StartTag"))
- # XXX Localization ...
- if "prompt" in token["data"]:
- prompt = token["data"]["prompt"]
- else:
- prompt = "This is a searchable index. Enter search keywords: "
- self.processCharacters(
- {"type": tokenTypes["Characters"], "data": prompt})
- attributes = token["data"].copy()
- if "action" in attributes:
- del attributes["action"]
- if "prompt" in attributes:
- del attributes["prompt"]
- attributes["name"] = "isindex"
- self.processStartTag(impliedTagToken("input", "StartTag",
- attributes=attributes,
- selfClosing=token["selfClosing"]))
- self.processEndTag(impliedTagToken("label"))
- self.processStartTag(impliedTagToken("hr", "StartTag"))
- self.processEndTag(impliedTagToken("form"))
-
- def startTagTextarea(self, token):
- self.tree.insertElement(token)
- self.parser.tokenizer.state = self.parser.tokenizer.rcdataState
- self.processSpaceCharacters = self.processSpaceCharactersDropNewline
- self.parser.framesetOK = False
-
- def startTagIFrame(self, token):
- self.parser.framesetOK = False
- self.startTagRawtext(token)
-
- def startTagNoscript(self, token):
- if self.parser.scripting:
- self.startTagRawtext(token)
- else:
- self.startTagOther(token)
-
- def startTagRawtext(self, token):
- """iframe, noembed noframes, noscript(if scripting enabled)"""
- self.parser.parseRCDataRawtext(token, "RAWTEXT")
-
- def startTagOpt(self, token):
- if self.tree.openElements[-1].name == "option":
- self.parser.phase.processEndTag(impliedTagToken("option"))
- self.tree.reconstructActiveFormattingElements()
- self.parser.tree.insertElement(token)
-
- def startTagSelect(self, token):
- self.tree.reconstructActiveFormattingElements()
- self.tree.insertElement(token)
- self.parser.framesetOK = False
- if self.parser.phase in (self.parser.phases["inTable"],
- self.parser.phases["inCaption"],
- self.parser.phases["inColumnGroup"],
- self.parser.phases["inTableBody"],
- self.parser.phases["inRow"],
- self.parser.phases["inCell"]):
- self.parser.phase = self.parser.phases["inSelectInTable"]
- else:
- self.parser.phase = self.parser.phases["inSelect"]
-
- def startTagRpRt(self, token):
- if self.tree.elementInScope("ruby"):
- self.tree.generateImpliedEndTags()
- if self.tree.openElements[-1].name != "ruby":
- self.parser.parseError()
- self.tree.insertElement(token)
-
- def startTagMath(self, token):
- self.tree.reconstructActiveFormattingElements()
- self.parser.adjustMathMLAttributes(token)
- self.parser.adjustForeignAttributes(token)
- token["namespace"] = namespaces["mathml"]
- self.tree.insertElement(token)
- # Need to get the parse error right for the case where the token
- # has a namespace not equal to the xmlns attribute
- if token["selfClosing"]:
- self.tree.openElements.pop()
- token["selfClosingAcknowledged"] = True
-
- def startTagSvg(self, token):
- self.tree.reconstructActiveFormattingElements()
- self.parser.adjustSVGAttributes(token)
- self.parser.adjustForeignAttributes(token)
- token["namespace"] = namespaces["svg"]
- self.tree.insertElement(token)
- # Need to get the parse error right for the case where the token
- # has a namespace not equal to the xmlns attribute
- if token["selfClosing"]:
- self.tree.openElements.pop()
- token["selfClosingAcknowledged"] = True
-
- def startTagMisplaced(self, token):
- """ Elements that should be children of other elements that have a
- different insertion mode; here they are ignored
- "caption", "col", "colgroup", "frame", "frameset", "head",
- "option", "optgroup", "tbody", "td", "tfoot", "th", "thead",
- "tr", "noscript"
- """
- self.parser.parseError("unexpected-start-tag-ignored", {"name": token["name"]})
-
- def startTagOther(self, token):
- self.tree.reconstructActiveFormattingElements()
- self.tree.insertElement(token)
-
- def endTagP(self, token):
- if not self.tree.elementInScope("p", variant="button"):
- self.startTagCloseP(impliedTagToken("p", "StartTag"))
- self.parser.parseError("unexpected-end-tag", {"name": "p"})
- self.endTagP(impliedTagToken("p", "EndTag"))
- else:
- self.tree.generateImpliedEndTags("p")
- if self.tree.openElements[-1].name != "p":
- self.parser.parseError("unexpected-end-tag", {"name": "p"})
- node = self.tree.openElements.pop()
- while node.name != "p":
- node = self.tree.openElements.pop()
-
- def endTagBody(self, token):
- if not self.tree.elementInScope("body"):
- self.parser.parseError()
- return
- elif self.tree.openElements[-1].name != "body":
- for node in self.tree.openElements[2:]:
- if node.name not in frozenset(("dd", "dt", "li", "optgroup",
- "option", "p", "rp", "rt",
- "tbody", "td", "tfoot",
- "th", "thead", "tr", "body",
- "html")):
- # Not sure this is the correct name for the parse error
- self.parser.parseError(
- "expected-one-end-tag-but-got-another",
- {"gotName": "body", "expectedName": node.name})
- break
- self.parser.phase = self.parser.phases["afterBody"]
-
- def endTagHtml(self, token):
- # We repeat the test for the body end tag token being ignored here
- if self.tree.elementInScope("body"):
- self.endTagBody(impliedTagToken("body"))
- return token
-
- def endTagBlock(self, token):
- # Put us back in the right whitespace handling mode
- if token["name"] == "pre":
- self.processSpaceCharacters = self.processSpaceCharactersNonPre
- inScope = self.tree.elementInScope(token["name"])
- if inScope:
- self.tree.generateImpliedEndTags()
- if self.tree.openElements[-1].name != token["name"]:
- self.parser.parseError("end-tag-too-early", {"name": token["name"]})
- if inScope:
- node = self.tree.openElements.pop()
- while node.name != token["name"]:
- node = self.tree.openElements.pop()
-
- def endTagForm(self, token):
- node = self.tree.formPointer
- self.tree.formPointer = None
- if node is None or not self.tree.elementInScope(node):
- self.parser.parseError("unexpected-end-tag",
- {"name": "form"})
- else:
- self.tree.generateImpliedEndTags()
- if self.tree.openElements[-1] != node:
- self.parser.parseError("end-tag-too-early-ignored",
- {"name": "form"})
- self.tree.openElements.remove(node)
-
- def endTagListItem(self, token):
- if token["name"] == "li":
- variant = "list"
- else:
- variant = None
- if not self.tree.elementInScope(token["name"], variant=variant):
- self.parser.parseError("unexpected-end-tag", {"name": token["name"]})
- else:
- self.tree.generateImpliedEndTags(exclude=token["name"])
- if self.tree.openElements[-1].name != token["name"]:
- self.parser.parseError(
- "end-tag-too-early",
- {"name": token["name"]})
- node = self.tree.openElements.pop()
- while node.name != token["name"]:
- node = self.tree.openElements.pop()
-
- def endTagHeading(self, token):
- for item in headingElements:
- if self.tree.elementInScope(item):
- self.tree.generateImpliedEndTags()
- break
- if self.tree.openElements[-1].name != token["name"]:
- self.parser.parseError("end-tag-too-early", {"name": token["name"]})
-
- for item in headingElements:
- if self.tree.elementInScope(item):
- item = self.tree.openElements.pop()
- while item.name not in headingElements:
- item = self.tree.openElements.pop()
- break
-
- def endTagFormatting(self, token):
- """The much-feared adoption agency algorithm"""
- # http://svn.whatwg.org/webapps/complete.html#adoptionAgency revision 7867
- # XXX Better parseError messages appreciated.
-
- # Step 1
- outerLoopCounter = 0
-
- # Step 2
- while outerLoopCounter < 8:
-
- # Step 3
- outerLoopCounter += 1
-
- # Step 4:
-
- # Let the formatting element be the last element in
- # the list of active formatting elements that:
- # - is between the end of the list and the last scope
- # marker in the list, if any, or the start of the list
- # otherwise, and
- # - has the same tag name as the token.
- formattingElement = self.tree.elementInActiveFormattingElements(
- token["name"])
- if (not formattingElement or
- (formattingElement in self.tree.openElements and
- not self.tree.elementInScope(formattingElement.name))):
- # If there is no such node, then abort these steps
- # and instead act as described in the "any other
- # end tag" entry below.
- self.endTagOther(token)
- return
-
- # Otherwise, if there is such a node, but that node is
- # not in the stack of open elements, then this is a
- # parse error; remove the element from the list, and
- # abort these steps.
- elif formattingElement not in self.tree.openElements:
- self.parser.parseError("adoption-agency-1.2", {"name": token["name"]})
- self.tree.activeFormattingElements.remove(formattingElement)
- return
-
- # Otherwise, if there is such a node, and that node is
- # also in the stack of open elements, but the element
- # is not in scope, then this is a parse error; ignore
- # the token, and abort these steps.
- elif not self.tree.elementInScope(formattingElement.name):
- self.parser.parseError("adoption-agency-4.4", {"name": token["name"]})
- return
-
- # Otherwise, there is a formatting element and that
- # element is in the stack and is in scope. If the
- # element is not the current node, this is a parse
- # error. In any case, proceed with the algorithm as
- # written in the following steps.
- else:
- if formattingElement != self.tree.openElements[-1]:
- self.parser.parseError("adoption-agency-1.3", {"name": token["name"]})
-
- # Step 5:
-
- # Let the furthest block be the topmost node in the
- # stack of open elements that is lower in the stack
- # than the formatting element, and is an element in
- # the special category. There might not be one.
- afeIndex = self.tree.openElements.index(formattingElement)
- furthestBlock = None
- for element in self.tree.openElements[afeIndex:]:
- if element.nameTuple in specialElements:
- furthestBlock = element
- break
-
- # Step 6:
-
- # If there is no furthest block, then the UA must
- # first pop all the nodes from the bottom of the stack
- # of open elements, from the current node up to and
- # including the formatting element, then remove the
- # formatting element from the list of active
- # formatting elements, and finally abort these steps.
- if furthestBlock is None:
- element = self.tree.openElements.pop()
- while element != formattingElement:
- element = self.tree.openElements.pop()
- self.tree.activeFormattingElements.remove(element)
- return
-
- # Step 7
- commonAncestor = self.tree.openElements[afeIndex - 1]
-
- # Step 8:
- # The bookmark is supposed to help us identify where to reinsert
- # nodes in step 15. We have to ensure that we reinsert nodes after
- # the node before the active formatting element. Note the bookmark
- # can move in step 9.7
- bookmark = self.tree.activeFormattingElements.index(formattingElement)
-
- # Step 9
- lastNode = node = furthestBlock
- innerLoopCounter = 0
-
- index = self.tree.openElements.index(node)
- while innerLoopCounter < 3:
- innerLoopCounter += 1
- # Node is element before node in open elements
- index -= 1
- node = self.tree.openElements[index]
- if node not in self.tree.activeFormattingElements:
- self.tree.openElements.remove(node)
- continue
- # Step 9.6
- if node == formattingElement:
- break
- # Step 9.7
- if lastNode == furthestBlock:
- bookmark = self.tree.activeFormattingElements.index(node) + 1
- # Step 9.8
- clone = node.cloneNode()
- # Replace node with clone
- self.tree.activeFormattingElements[
- self.tree.activeFormattingElements.index(node)] = clone
- self.tree.openElements[
- self.tree.openElements.index(node)] = clone
- node = clone
- # Step 9.9
- # Remove lastNode from its parents, if any
- if lastNode.parent:
- lastNode.parent.removeChild(lastNode)
- node.appendChild(lastNode)
- # Step 9.10
- lastNode = node
-
- # Step 10
- # Foster parent lastNode if commonAncestor is a
- # table, tbody, tfoot, thead, or tr we need to foster
- # parent the lastNode
- if lastNode.parent:
- lastNode.parent.removeChild(lastNode)
-
- if commonAncestor.name in frozenset(("table", "tbody", "tfoot", "thead", "tr")):
- parent, insertBefore = self.tree.getTableMisnestedNodePosition()
- parent.insertBefore(lastNode, insertBefore)
- else:
- commonAncestor.appendChild(lastNode)
-
- # Step 11
- clone = formattingElement.cloneNode()
-
- # Step 12
- furthestBlock.reparentChildren(clone)
-
- # Step 13
- furthestBlock.appendChild(clone)
-
- # Step 14
- self.tree.activeFormattingElements.remove(formattingElement)
- self.tree.activeFormattingElements.insert(bookmark, clone)
-
- # Step 15
- self.tree.openElements.remove(formattingElement)
- self.tree.openElements.insert(
- self.tree.openElements.index(furthestBlock) + 1, clone)
-
- def endTagAppletMarqueeObject(self, token):
- if self.tree.elementInScope(token["name"]):
- self.tree.generateImpliedEndTags()
- if self.tree.openElements[-1].name != token["name"]:
- self.parser.parseError("end-tag-too-early", {"name": token["name"]})
-
- if self.tree.elementInScope(token["name"]):
- element = self.tree.openElements.pop()
- while element.name != token["name"]:
- element = self.tree.openElements.pop()
- self.tree.clearActiveFormattingElements()
-
- def endTagBr(self, token):
- self.parser.parseError("unexpected-end-tag-treated-as",
- {"originalName": "br", "newName": "br element"})
- self.tree.reconstructActiveFormattingElements()
- self.tree.insertElement(impliedTagToken("br", "StartTag"))
- self.tree.openElements.pop()
-
- def endTagOther(self, token):
- for node in self.tree.openElements[::-1]:
- if node.name == token["name"]:
- self.tree.generateImpliedEndTags(exclude=token["name"])
- if self.tree.openElements[-1].name != token["name"]:
- self.parser.parseError("unexpected-end-tag", {"name": token["name"]})
- while self.tree.openElements.pop() != node:
- pass
- break
- else:
- if node.nameTuple in specialElements:
- self.parser.parseError("unexpected-end-tag", {"name": token["name"]})
- break
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", Phase.startTagHtml),
- (("base", "basefont", "bgsound", "command", "link", "meta",
- "script", "style", "title"),
- startTagProcessInHead),
- ("body", startTagBody),
- ("frameset", startTagFrameset),
- (("address", "article", "aside", "blockquote", "center", "details",
- "dir", "div", "dl", "fieldset", "figcaption", "figure",
- "footer", "header", "hgroup", "main", "menu", "nav", "ol", "p",
- "section", "summary", "ul"),
- startTagCloseP),
- (headingElements, startTagHeading),
- (("pre", "listing"), startTagPreListing),
- ("form", startTagForm),
- (("li", "dd", "dt"), startTagListItem),
- ("plaintext", startTagPlaintext),
- ("a", startTagA),
- (("b", "big", "code", "em", "font", "i", "s", "small", "strike",
- "strong", "tt", "u"), startTagFormatting),
- ("nobr", startTagNobr),
- ("button", startTagButton),
- (("applet", "marquee", "object"), startTagAppletMarqueeObject),
- ("xmp", startTagXmp),
- ("table", startTagTable),
- (("area", "br", "embed", "img", "keygen", "wbr"),
- startTagVoidFormatting),
- (("param", "source", "track"), startTagParamSource),
- ("input", startTagInput),
- ("hr", startTagHr),
- ("image", startTagImage),
- ("isindex", startTagIsIndex),
- ("textarea", startTagTextarea),
- ("iframe", startTagIFrame),
- ("noscript", startTagNoscript),
- (("noembed", "noframes"), startTagRawtext),
- ("select", startTagSelect),
- (("rp", "rt"), startTagRpRt),
- (("option", "optgroup"), startTagOpt),
- (("math"), startTagMath),
- (("svg"), startTagSvg),
- (("caption", "col", "colgroup", "frame", "head",
- "tbody", "td", "tfoot", "th", "thead",
- "tr"), startTagMisplaced)
- ])
- startTagHandler.default = startTagOther
-
- endTagHandler = _utils.MethodDispatcher([
- ("body", endTagBody),
- ("html", endTagHtml),
- (("address", "article", "aside", "blockquote", "button", "center",
- "details", "dialog", "dir", "div", "dl", "fieldset", "figcaption", "figure",
- "footer", "header", "hgroup", "listing", "main", "menu", "nav", "ol", "pre",
- "section", "summary", "ul"), endTagBlock),
- ("form", endTagForm),
- ("p", endTagP),
- (("dd", "dt", "li"), endTagListItem),
- (headingElements, endTagHeading),
- (("a", "b", "big", "code", "em", "font", "i", "nobr", "s", "small",
- "strike", "strong", "tt", "u"), endTagFormatting),
- (("applet", "marquee", "object"), endTagAppletMarqueeObject),
- ("br", endTagBr),
- ])
- endTagHandler.default = endTagOther
-
- class TextPhase(Phase):
- __slots__ = tuple()
-
- def processCharacters(self, token):
- self.tree.insertText(token["data"])
-
- def processEOF(self):
- self.parser.parseError("expected-named-closing-tag-but-got-eof",
- {"name": self.tree.openElements[-1].name})
- self.tree.openElements.pop()
- self.parser.phase = self.parser.originalPhase
- return True
-
- def startTagOther(self, token):
- assert False, "Tried to process start tag %s in RCDATA/RAWTEXT mode" % token['name']
-
- def endTagScript(self, token):
- node = self.tree.openElements.pop()
- assert node.name == "script"
- self.parser.phase = self.parser.originalPhase
- # The rest of this method is all stuff that only happens if
- # document.write works
-
- def endTagOther(self, token):
- self.tree.openElements.pop()
- self.parser.phase = self.parser.originalPhase
-
- startTagHandler = _utils.MethodDispatcher([])
- startTagHandler.default = startTagOther
- endTagHandler = _utils.MethodDispatcher([
- ("script", endTagScript)])
- endTagHandler.default = endTagOther
-
- class InTablePhase(Phase):
- # http://www.whatwg.org/specs/web-apps/current-work/#in-table
- __slots__ = tuple()
-
- # helper methods
- def clearStackToTableContext(self):
- # "clear the stack back to a table context"
- while self.tree.openElements[-1].name not in ("table", "html"):
- # self.parser.parseError("unexpected-implied-end-tag-in-table",
- # {"name": self.tree.openElements[-1].name})
- self.tree.openElements.pop()
- # When the current node is <html> it's an innerHTML case
-
- # processing methods
- def processEOF(self):
- if self.tree.openElements[-1].name != "html":
- self.parser.parseError("eof-in-table")
- else:
- assert self.parser.innerHTML
- # Stop parsing
-
- def processSpaceCharacters(self, token):
- originalPhase = self.parser.phase
- self.parser.phase = self.parser.phases["inTableText"]
- self.parser.phase.originalPhase = originalPhase
- self.parser.phase.processSpaceCharacters(token)
-
- def processCharacters(self, token):
- originalPhase = self.parser.phase
- self.parser.phase = self.parser.phases["inTableText"]
- self.parser.phase.originalPhase = originalPhase
- self.parser.phase.processCharacters(token)
-
- def insertText(self, token):
- # If we get here there must be at least one non-whitespace character
- # Do the table magic!
- self.tree.insertFromTable = True
- self.parser.phases["inBody"].processCharacters(token)
- self.tree.insertFromTable = False
-
- def startTagCaption(self, token):
- self.clearStackToTableContext()
- self.tree.activeFormattingElements.append(Marker)
- self.tree.insertElement(token)
- self.parser.phase = self.parser.phases["inCaption"]
-
- def startTagColgroup(self, token):
- self.clearStackToTableContext()
- self.tree.insertElement(token)
- self.parser.phase = self.parser.phases["inColumnGroup"]
-
- def startTagCol(self, token):
- self.startTagColgroup(impliedTagToken("colgroup", "StartTag"))
- return token
-
- def startTagRowGroup(self, token):
- self.clearStackToTableContext()
- self.tree.insertElement(token)
- self.parser.phase = self.parser.phases["inTableBody"]
-
- def startTagImplyTbody(self, token):
- self.startTagRowGroup(impliedTagToken("tbody", "StartTag"))
- return token
-
- def startTagTable(self, token):
- self.parser.parseError("unexpected-start-tag-implies-end-tag",
- {"startName": "table", "endName": "table"})
- self.parser.phase.processEndTag(impliedTagToken("table"))
- if not self.parser.innerHTML:
- return token
-
- def startTagStyleScript(self, token):
- return self.parser.phases["inHead"].processStartTag(token)
-
- def startTagInput(self, token):
- if ("type" in token["data"] and
- token["data"]["type"].translate(asciiUpper2Lower) == "hidden"):
- self.parser.parseError("unexpected-hidden-input-in-table")
- self.tree.insertElement(token)
- # XXX associate with form
- self.tree.openElements.pop()
- else:
- self.startTagOther(token)
-
- def startTagForm(self, token):
- self.parser.parseError("unexpected-form-in-table")
- if self.tree.formPointer is None:
- self.tree.insertElement(token)
- self.tree.formPointer = self.tree.openElements[-1]
- self.tree.openElements.pop()
-
- def startTagOther(self, token):
- self.parser.parseError("unexpected-start-tag-implies-table-voodoo", {"name": token["name"]})
- # Do the table magic!
- self.tree.insertFromTable = True
- self.parser.phases["inBody"].processStartTag(token)
- self.tree.insertFromTable = False
-
- def endTagTable(self, token):
- if self.tree.elementInScope("table", variant="table"):
- self.tree.generateImpliedEndTags()
- if self.tree.openElements[-1].name != "table":
- self.parser.parseError("end-tag-too-early-named",
- {"gotName": "table",
- "expectedName": self.tree.openElements[-1].name})
- while self.tree.openElements[-1].name != "table":
- self.tree.openElements.pop()
- self.tree.openElements.pop()
- self.parser.resetInsertionMode()
- else:
- # innerHTML case
- assert self.parser.innerHTML
- self.parser.parseError()
-
- def endTagIgnore(self, token):
- self.parser.parseError("unexpected-end-tag", {"name": token["name"]})
-
- def endTagOther(self, token):
- self.parser.parseError("unexpected-end-tag-implies-table-voodoo", {"name": token["name"]})
- # Do the table magic!
- self.tree.insertFromTable = True
- self.parser.phases["inBody"].processEndTag(token)
- self.tree.insertFromTable = False
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", Phase.startTagHtml),
- ("caption", startTagCaption),
- ("colgroup", startTagColgroup),
- ("col", startTagCol),
- (("tbody", "tfoot", "thead"), startTagRowGroup),
- (("td", "th", "tr"), startTagImplyTbody),
- ("table", startTagTable),
- (("style", "script"), startTagStyleScript),
- ("input", startTagInput),
- ("form", startTagForm)
- ])
- startTagHandler.default = startTagOther
-
- endTagHandler = _utils.MethodDispatcher([
- ("table", endTagTable),
- (("body", "caption", "col", "colgroup", "html", "tbody", "td",
- "tfoot", "th", "thead", "tr"), endTagIgnore)
- ])
- endTagHandler.default = endTagOther
-
- class InTableTextPhase(Phase):
- __slots__ = ("originalPhase", "characterTokens")
-
- def __init__(self, *args, **kwargs):
- super(InTableTextPhase, self).__init__(*args, **kwargs)
- self.originalPhase = None
- self.characterTokens = []
-
- def flushCharacters(self):
- data = "".join([item["data"] for item in self.characterTokens])
- if any([item not in spaceCharacters for item in data]):
- token = {"type": tokenTypes["Characters"], "data": data}
- self.parser.phases["inTable"].insertText(token)
- elif data:
- self.tree.insertText(data)
- self.characterTokens = []
-
- def processComment(self, token):
- self.flushCharacters()
- self.parser.phase = self.originalPhase
- return token
-
- def processEOF(self):
- self.flushCharacters()
- self.parser.phase = self.originalPhase
- return True
-
- def processCharacters(self, token):
- if token["data"] == "\u0000":
- return
- self.characterTokens.append(token)
-
- def processSpaceCharacters(self, token):
- # pretty sure we should never reach here
- self.characterTokens.append(token)
- # assert False
-
- def processStartTag(self, token):
- self.flushCharacters()
- self.parser.phase = self.originalPhase
- return token
-
- def processEndTag(self, token):
- self.flushCharacters()
- self.parser.phase = self.originalPhase
- return token
-
- class InCaptionPhase(Phase):
- # http://www.whatwg.org/specs/web-apps/current-work/#in-caption
- __slots__ = tuple()
-
- def ignoreEndTagCaption(self):
- return not self.tree.elementInScope("caption", variant="table")
-
- def processEOF(self):
- self.parser.phases["inBody"].processEOF()
-
- def processCharacters(self, token):
- return self.parser.phases["inBody"].processCharacters(token)
-
- def startTagTableElement(self, token):
- self.parser.parseError()
- # XXX Have to duplicate logic here to find out if the tag is ignored
- ignoreEndTag = self.ignoreEndTagCaption()
- self.parser.phase.processEndTag(impliedTagToken("caption"))
- if not ignoreEndTag:
- return token
-
- def startTagOther(self, token):
- return self.parser.phases["inBody"].processStartTag(token)
-
- def endTagCaption(self, token):
- if not self.ignoreEndTagCaption():
- # AT this code is quite similar to endTagTable in "InTable"
- self.tree.generateImpliedEndTags()
- if self.tree.openElements[-1].name != "caption":
- self.parser.parseError("expected-one-end-tag-but-got-another",
- {"gotName": "caption",
- "expectedName": self.tree.openElements[-1].name})
- while self.tree.openElements[-1].name != "caption":
- self.tree.openElements.pop()
- self.tree.openElements.pop()
- self.tree.clearActiveFormattingElements()
- self.parser.phase = self.parser.phases["inTable"]
- else:
- # innerHTML case
- assert self.parser.innerHTML
- self.parser.parseError()
-
- def endTagTable(self, token):
- self.parser.parseError()
- ignoreEndTag = self.ignoreEndTagCaption()
- self.parser.phase.processEndTag(impliedTagToken("caption"))
- if not ignoreEndTag:
- return token
-
- def endTagIgnore(self, token):
- self.parser.parseError("unexpected-end-tag", {"name": token["name"]})
-
- def endTagOther(self, token):
- return self.parser.phases["inBody"].processEndTag(token)
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", Phase.startTagHtml),
- (("caption", "col", "colgroup", "tbody", "td", "tfoot", "th",
- "thead", "tr"), startTagTableElement)
- ])
- startTagHandler.default = startTagOther
-
- endTagHandler = _utils.MethodDispatcher([
- ("caption", endTagCaption),
- ("table", endTagTable),
- (("body", "col", "colgroup", "html", "tbody", "td", "tfoot", "th",
- "thead", "tr"), endTagIgnore)
- ])
- endTagHandler.default = endTagOther
-
- class InColumnGroupPhase(Phase):
- # http://www.whatwg.org/specs/web-apps/current-work/#in-column
- __slots__ = tuple()
-
- def ignoreEndTagColgroup(self):
- return self.tree.openElements[-1].name == "html"
-
- def processEOF(self):
- if self.tree.openElements[-1].name == "html":
- assert self.parser.innerHTML
- return
- else:
- ignoreEndTag = self.ignoreEndTagColgroup()
- self.endTagColgroup(impliedTagToken("colgroup"))
- if not ignoreEndTag:
- return True
-
- def processCharacters(self, token):
- ignoreEndTag = self.ignoreEndTagColgroup()
- self.endTagColgroup(impliedTagToken("colgroup"))
- if not ignoreEndTag:
- return token
-
- def startTagCol(self, token):
- self.tree.insertElement(token)
- self.tree.openElements.pop()
- token["selfClosingAcknowledged"] = True
-
- def startTagOther(self, token):
- ignoreEndTag = self.ignoreEndTagColgroup()
- self.endTagColgroup(impliedTagToken("colgroup"))
- if not ignoreEndTag:
- return token
-
- def endTagColgroup(self, token):
- if self.ignoreEndTagColgroup():
- # innerHTML case
- assert self.parser.innerHTML
- self.parser.parseError()
- else:
- self.tree.openElements.pop()
- self.parser.phase = self.parser.phases["inTable"]
-
- def endTagCol(self, token):
- self.parser.parseError("no-end-tag", {"name": "col"})
-
- def endTagOther(self, token):
- ignoreEndTag = self.ignoreEndTagColgroup()
- self.endTagColgroup(impliedTagToken("colgroup"))
- if not ignoreEndTag:
- return token
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", Phase.startTagHtml),
- ("col", startTagCol)
- ])
- startTagHandler.default = startTagOther
-
- endTagHandler = _utils.MethodDispatcher([
- ("colgroup", endTagColgroup),
- ("col", endTagCol)
- ])
- endTagHandler.default = endTagOther
-
- class InTableBodyPhase(Phase):
- # http://www.whatwg.org/specs/web-apps/current-work/#in-table0
- __slots__ = tuple()
-
- # helper methods
- def clearStackToTableBodyContext(self):
- while self.tree.openElements[-1].name not in ("tbody", "tfoot",
- "thead", "html"):
- # self.parser.parseError("unexpected-implied-end-tag-in-table",
- # {"name": self.tree.openElements[-1].name})
- self.tree.openElements.pop()
- if self.tree.openElements[-1].name == "html":
- assert self.parser.innerHTML
-
- # the rest
- def processEOF(self):
- self.parser.phases["inTable"].processEOF()
-
- def processSpaceCharacters(self, token):
- return self.parser.phases["inTable"].processSpaceCharacters(token)
-
- def processCharacters(self, token):
- return self.parser.phases["inTable"].processCharacters(token)
-
- def startTagTr(self, token):
- self.clearStackToTableBodyContext()
- self.tree.insertElement(token)
- self.parser.phase = self.parser.phases["inRow"]
-
- def startTagTableCell(self, token):
- self.parser.parseError("unexpected-cell-in-table-body",
- {"name": token["name"]})
- self.startTagTr(impliedTagToken("tr", "StartTag"))
- return token
-
- def startTagTableOther(self, token):
- # XXX AT Any ideas on how to share this with endTagTable?
- if (self.tree.elementInScope("tbody", variant="table") or
- self.tree.elementInScope("thead", variant="table") or
- self.tree.elementInScope("tfoot", variant="table")):
- self.clearStackToTableBodyContext()
- self.endTagTableRowGroup(
- impliedTagToken(self.tree.openElements[-1].name))
- return token
- else:
- # innerHTML case
- assert self.parser.innerHTML
- self.parser.parseError()
-
- def startTagOther(self, token):
- return self.parser.phases["inTable"].processStartTag(token)
-
- def endTagTableRowGroup(self, token):
- if self.tree.elementInScope(token["name"], variant="table"):
- self.clearStackToTableBodyContext()
- self.tree.openElements.pop()
- self.parser.phase = self.parser.phases["inTable"]
- else:
- self.parser.parseError("unexpected-end-tag-in-table-body",
- {"name": token["name"]})
-
- def endTagTable(self, token):
- if (self.tree.elementInScope("tbody", variant="table") or
- self.tree.elementInScope("thead", variant="table") or
- self.tree.elementInScope("tfoot", variant="table")):
- self.clearStackToTableBodyContext()
- self.endTagTableRowGroup(
- impliedTagToken(self.tree.openElements[-1].name))
- return token
- else:
- # innerHTML case
- assert self.parser.innerHTML
- self.parser.parseError()
-
- def endTagIgnore(self, token):
- self.parser.parseError("unexpected-end-tag-in-table-body",
- {"name": token["name"]})
-
- def endTagOther(self, token):
- return self.parser.phases["inTable"].processEndTag(token)
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", Phase.startTagHtml),
- ("tr", startTagTr),
- (("td", "th"), startTagTableCell),
- (("caption", "col", "colgroup", "tbody", "tfoot", "thead"),
- startTagTableOther)
- ])
- startTagHandler.default = startTagOther
-
- endTagHandler = _utils.MethodDispatcher([
- (("tbody", "tfoot", "thead"), endTagTableRowGroup),
- ("table", endTagTable),
- (("body", "caption", "col", "colgroup", "html", "td", "th",
- "tr"), endTagIgnore)
- ])
- endTagHandler.default = endTagOther
-
- class InRowPhase(Phase):
- # http://www.whatwg.org/specs/web-apps/current-work/#in-row
- __slots__ = tuple()
-
- # helper methods (XXX unify this with other table helper methods)
- def clearStackToTableRowContext(self):
- while self.tree.openElements[-1].name not in ("tr", "html"):
- self.parser.parseError("unexpected-implied-end-tag-in-table-row",
- {"name": self.tree.openElements[-1].name})
- self.tree.openElements.pop()
-
- def ignoreEndTagTr(self):
- return not self.tree.elementInScope("tr", variant="table")
-
- # the rest
- def processEOF(self):
- self.parser.phases["inTable"].processEOF()
-
- def processSpaceCharacters(self, token):
- return self.parser.phases["inTable"].processSpaceCharacters(token)
-
- def processCharacters(self, token):
- return self.parser.phases["inTable"].processCharacters(token)
-
- def startTagTableCell(self, token):
- self.clearStackToTableRowContext()
- self.tree.insertElement(token)
- self.parser.phase = self.parser.phases["inCell"]
- self.tree.activeFormattingElements.append(Marker)
-
- def startTagTableOther(self, token):
- ignoreEndTag = self.ignoreEndTagTr()
- self.endTagTr(impliedTagToken("tr"))
- # XXX how are we sure it's always ignored in the innerHTML case?
- if not ignoreEndTag:
- return token
-
- def startTagOther(self, token):
- return self.parser.phases["inTable"].processStartTag(token)
-
- def endTagTr(self, token):
- if not self.ignoreEndTagTr():
- self.clearStackToTableRowContext()
- self.tree.openElements.pop()
- self.parser.phase = self.parser.phases["inTableBody"]
- else:
- # innerHTML case
- assert self.parser.innerHTML
- self.parser.parseError()
-
- def endTagTable(self, token):
- ignoreEndTag = self.ignoreEndTagTr()
- self.endTagTr(impliedTagToken("tr"))
- # Reprocess the current tag if the tr end tag was not ignored
- # XXX how are we sure it's always ignored in the innerHTML case?
- if not ignoreEndTag:
- return token
-
- def endTagTableRowGroup(self, token):
- if self.tree.elementInScope(token["name"], variant="table"):
- self.endTagTr(impliedTagToken("tr"))
- return token
- else:
- self.parser.parseError()
-
- def endTagIgnore(self, token):
- self.parser.parseError("unexpected-end-tag-in-table-row",
- {"name": token["name"]})
-
- def endTagOther(self, token):
- return self.parser.phases["inTable"].processEndTag(token)
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", Phase.startTagHtml),
- (("td", "th"), startTagTableCell),
- (("caption", "col", "colgroup", "tbody", "tfoot", "thead",
- "tr"), startTagTableOther)
- ])
- startTagHandler.default = startTagOther
-
- endTagHandler = _utils.MethodDispatcher([
- ("tr", endTagTr),
- ("table", endTagTable),
- (("tbody", "tfoot", "thead"), endTagTableRowGroup),
- (("body", "caption", "col", "colgroup", "html", "td", "th"),
- endTagIgnore)
- ])
- endTagHandler.default = endTagOther
-
- class InCellPhase(Phase):
- # http://www.whatwg.org/specs/web-apps/current-work/#in-cell
- __slots__ = tuple()
-
- # helper
- def closeCell(self):
- if self.tree.elementInScope("td", variant="table"):
- self.endTagTableCell(impliedTagToken("td"))
- elif self.tree.elementInScope("th", variant="table"):
- self.endTagTableCell(impliedTagToken("th"))
-
- # the rest
- def processEOF(self):
- self.parser.phases["inBody"].processEOF()
-
- def processCharacters(self, token):
- return self.parser.phases["inBody"].processCharacters(token)
-
- def startTagTableOther(self, token):
- if (self.tree.elementInScope("td", variant="table") or
- self.tree.elementInScope("th", variant="table")):
- self.closeCell()
- return token
- else:
- # innerHTML case
- assert self.parser.innerHTML
- self.parser.parseError()
-
- def startTagOther(self, token):
- return self.parser.phases["inBody"].processStartTag(token)
-
- def endTagTableCell(self, token):
- if self.tree.elementInScope(token["name"], variant="table"):
- self.tree.generateImpliedEndTags(token["name"])
- if self.tree.openElements[-1].name != token["name"]:
- self.parser.parseError("unexpected-cell-end-tag",
- {"name": token["name"]})
- while True:
- node = self.tree.openElements.pop()
- if node.name == token["name"]:
- break
- else:
- self.tree.openElements.pop()
- self.tree.clearActiveFormattingElements()
- self.parser.phase = self.parser.phases["inRow"]
- else:
- self.parser.parseError("unexpected-end-tag", {"name": token["name"]})
-
- def endTagIgnore(self, token):
- self.parser.parseError("unexpected-end-tag", {"name": token["name"]})
-
- def endTagImply(self, token):
- if self.tree.elementInScope(token["name"], variant="table"):
- self.closeCell()
- return token
- else:
- # sometimes innerHTML case
- self.parser.parseError()
-
- def endTagOther(self, token):
- return self.parser.phases["inBody"].processEndTag(token)
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", Phase.startTagHtml),
- (("caption", "col", "colgroup", "tbody", "td", "tfoot", "th",
- "thead", "tr"), startTagTableOther)
- ])
- startTagHandler.default = startTagOther
-
- endTagHandler = _utils.MethodDispatcher([
- (("td", "th"), endTagTableCell),
- (("body", "caption", "col", "colgroup", "html"), endTagIgnore),
- (("table", "tbody", "tfoot", "thead", "tr"), endTagImply)
- ])
- endTagHandler.default = endTagOther
-
- class InSelectPhase(Phase):
- __slots__ = tuple()
-
- # http://www.whatwg.org/specs/web-apps/current-work/#in-select
- def processEOF(self):
- if self.tree.openElements[-1].name != "html":
- self.parser.parseError("eof-in-select")
- else:
- assert self.parser.innerHTML
-
- def processCharacters(self, token):
- if token["data"] == "\u0000":
- return
- self.tree.insertText(token["data"])
-
- def startTagOption(self, token):
- # We need to imply </option> if <option> is the current node.
- if self.tree.openElements[-1].name == "option":
- self.tree.openElements.pop()
- self.tree.insertElement(token)
-
- def startTagOptgroup(self, token):
- if self.tree.openElements[-1].name == "option":
- self.tree.openElements.pop()
- if self.tree.openElements[-1].name == "optgroup":
- self.tree.openElements.pop()
- self.tree.insertElement(token)
-
- def startTagSelect(self, token):
- self.parser.parseError("unexpected-select-in-select")
- self.endTagSelect(impliedTagToken("select"))
-
- def startTagInput(self, token):
- self.parser.parseError("unexpected-input-in-select")
- if self.tree.elementInScope("select", variant="select"):
- self.endTagSelect(impliedTagToken("select"))
- return token
- else:
- assert self.parser.innerHTML
-
- def startTagScript(self, token):
- return self.parser.phases["inHead"].processStartTag(token)
-
- def startTagOther(self, token):
- self.parser.parseError("unexpected-start-tag-in-select",
- {"name": token["name"]})
-
- def endTagOption(self, token):
- if self.tree.openElements[-1].name == "option":
- self.tree.openElements.pop()
- else:
- self.parser.parseError("unexpected-end-tag-in-select",
- {"name": "option"})
-
- def endTagOptgroup(self, token):
- # </optgroup> implicitly closes <option>
- if (self.tree.openElements[-1].name == "option" and
- self.tree.openElements[-2].name == "optgroup"):
- self.tree.openElements.pop()
- # It also closes </optgroup>
- if self.tree.openElements[-1].name == "optgroup":
- self.tree.openElements.pop()
- # But nothing else
- else:
- self.parser.parseError("unexpected-end-tag-in-select",
- {"name": "optgroup"})
-
- def endTagSelect(self, token):
- if self.tree.elementInScope("select", variant="select"):
- node = self.tree.openElements.pop()
- while node.name != "select":
- node = self.tree.openElements.pop()
- self.parser.resetInsertionMode()
- else:
- # innerHTML case
- assert self.parser.innerHTML
- self.parser.parseError()
-
- def endTagOther(self, token):
- self.parser.parseError("unexpected-end-tag-in-select",
- {"name": token["name"]})
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", Phase.startTagHtml),
- ("option", startTagOption),
- ("optgroup", startTagOptgroup),
- ("select", startTagSelect),
- (("input", "keygen", "textarea"), startTagInput),
- ("script", startTagScript)
- ])
- startTagHandler.default = startTagOther
-
- endTagHandler = _utils.MethodDispatcher([
- ("option", endTagOption),
- ("optgroup", endTagOptgroup),
- ("select", endTagSelect)
- ])
- endTagHandler.default = endTagOther
-
- class InSelectInTablePhase(Phase):
- __slots__ = tuple()
-
- def processEOF(self):
- self.parser.phases["inSelect"].processEOF()
-
- def processCharacters(self, token):
- return self.parser.phases["inSelect"].processCharacters(token)
-
- def startTagTable(self, token):
- self.parser.parseError("unexpected-table-element-start-tag-in-select-in-table", {"name": token["name"]})
- self.endTagOther(impliedTagToken("select"))
- return token
-
- def startTagOther(self, token):
- return self.parser.phases["inSelect"].processStartTag(token)
-
- def endTagTable(self, token):
- self.parser.parseError("unexpected-table-element-end-tag-in-select-in-table", {"name": token["name"]})
- if self.tree.elementInScope(token["name"], variant="table"):
- self.endTagOther(impliedTagToken("select"))
- return token
-
- def endTagOther(self, token):
- return self.parser.phases["inSelect"].processEndTag(token)
-
- startTagHandler = _utils.MethodDispatcher([
- (("caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th"),
- startTagTable)
- ])
- startTagHandler.default = startTagOther
-
- endTagHandler = _utils.MethodDispatcher([
- (("caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th"),
- endTagTable)
- ])
- endTagHandler.default = endTagOther
-
- class InForeignContentPhase(Phase):
- __slots__ = tuple()
-
- breakoutElements = frozenset(["b", "big", "blockquote", "body", "br",
- "center", "code", "dd", "div", "dl", "dt",
- "em", "embed", "h1", "h2", "h3",
- "h4", "h5", "h6", "head", "hr", "i", "img",
- "li", "listing", "menu", "meta", "nobr",
- "ol", "p", "pre", "ruby", "s", "small",
- "span", "strong", "strike", "sub", "sup",
- "table", "tt", "u", "ul", "var"])
-
- def adjustSVGTagNames(self, token):
- replacements = {"altglyph": "altGlyph",
- "altglyphdef": "altGlyphDef",
- "altglyphitem": "altGlyphItem",
- "animatecolor": "animateColor",
- "animatemotion": "animateMotion",
- "animatetransform": "animateTransform",
- "clippath": "clipPath",
- "feblend": "feBlend",
- "fecolormatrix": "feColorMatrix",
- "fecomponenttransfer": "feComponentTransfer",
- "fecomposite": "feComposite",
- "feconvolvematrix": "feConvolveMatrix",
- "fediffuselighting": "feDiffuseLighting",
- "fedisplacementmap": "feDisplacementMap",
- "fedistantlight": "feDistantLight",
- "feflood": "feFlood",
- "fefunca": "feFuncA",
- "fefuncb": "feFuncB",
- "fefuncg": "feFuncG",
- "fefuncr": "feFuncR",
- "fegaussianblur": "feGaussianBlur",
- "feimage": "feImage",
- "femerge": "feMerge",
- "femergenode": "feMergeNode",
- "femorphology": "feMorphology",
- "feoffset": "feOffset",
- "fepointlight": "fePointLight",
- "fespecularlighting": "feSpecularLighting",
- "fespotlight": "feSpotLight",
- "fetile": "feTile",
- "feturbulence": "feTurbulence",
- "foreignobject": "foreignObject",
- "glyphref": "glyphRef",
- "lineargradient": "linearGradient",
- "radialgradient": "radialGradient",
- "textpath": "textPath"}
-
- if token["name"] in replacements:
- token["name"] = replacements[token["name"]]
-
- def processCharacters(self, token):
- if token["data"] == "\u0000":
- token["data"] = "\uFFFD"
- elif (self.parser.framesetOK and
- any(char not in spaceCharacters for char in token["data"])):
- self.parser.framesetOK = False
- Phase.processCharacters(self, token)
-
- def processStartTag(self, token):
- currentNode = self.tree.openElements[-1]
- if (token["name"] in self.breakoutElements or
- (token["name"] == "font" and
- set(token["data"].keys()) & {"color", "face", "size"})):
- self.parser.parseError("unexpected-html-element-in-foreign-content",
- {"name": token["name"]})
- while (self.tree.openElements[-1].namespace !=
- self.tree.defaultNamespace and
- not self.parser.isHTMLIntegrationPoint(self.tree.openElements[-1]) and
- not self.parser.isMathMLTextIntegrationPoint(self.tree.openElements[-1])):
- self.tree.openElements.pop()
- return token
-
- else:
- if currentNode.namespace == namespaces["mathml"]:
- self.parser.adjustMathMLAttributes(token)
- elif currentNode.namespace == namespaces["svg"]:
- self.adjustSVGTagNames(token)
- self.parser.adjustSVGAttributes(token)
- self.parser.adjustForeignAttributes(token)
- token["namespace"] = currentNode.namespace
- self.tree.insertElement(token)
- if token["selfClosing"]:
- self.tree.openElements.pop()
- token["selfClosingAcknowledged"] = True
-
- def processEndTag(self, token):
- nodeIndex = len(self.tree.openElements) - 1
- node = self.tree.openElements[-1]
- if node.name.translate(asciiUpper2Lower) != token["name"]:
- self.parser.parseError("unexpected-end-tag", {"name": token["name"]})
-
- while True:
- if node.name.translate(asciiUpper2Lower) == token["name"]:
- # XXX this isn't in the spec but it seems necessary
- if self.parser.phase == self.parser.phases["inTableText"]:
- self.parser.phase.flushCharacters()
- self.parser.phase = self.parser.phase.originalPhase
- while self.tree.openElements.pop() != node:
- assert self.tree.openElements
- new_token = None
- break
- nodeIndex -= 1
-
- node = self.tree.openElements[nodeIndex]
- if node.namespace != self.tree.defaultNamespace:
- continue
- else:
- new_token = self.parser.phase.processEndTag(token)
- break
- return new_token
-
- class AfterBodyPhase(Phase):
- __slots__ = tuple()
-
- def processEOF(self):
- # Stop parsing
- pass
-
- def processComment(self, token):
- # This is needed because data is to be appended to the <html> element
- # here and not to whatever is currently open.
- self.tree.insertComment(token, self.tree.openElements[0])
-
- def processCharacters(self, token):
- self.parser.parseError("unexpected-char-after-body")
- self.parser.phase = self.parser.phases["inBody"]
- return token
-
- def startTagHtml(self, token):
- return self.parser.phases["inBody"].processStartTag(token)
-
- def startTagOther(self, token):
- self.parser.parseError("unexpected-start-tag-after-body",
- {"name": token["name"]})
- self.parser.phase = self.parser.phases["inBody"]
- return token
-
- def endTagHtml(self, name):
- if self.parser.innerHTML:
- self.parser.parseError("unexpected-end-tag-after-body-innerhtml")
- else:
- self.parser.phase = self.parser.phases["afterAfterBody"]
-
- def endTagOther(self, token):
- self.parser.parseError("unexpected-end-tag-after-body",
- {"name": token["name"]})
- self.parser.phase = self.parser.phases["inBody"]
- return token
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", startTagHtml)
- ])
- startTagHandler.default = startTagOther
-
- endTagHandler = _utils.MethodDispatcher([("html", endTagHtml)])
- endTagHandler.default = endTagOther
-
- class InFramesetPhase(Phase):
- # http://www.whatwg.org/specs/web-apps/current-work/#in-frameset
- __slots__ = tuple()
-
- def processEOF(self):
- if self.tree.openElements[-1].name != "html":
- self.parser.parseError("eof-in-frameset")
- else:
- assert self.parser.innerHTML
-
- def processCharacters(self, token):
- self.parser.parseError("unexpected-char-in-frameset")
-
- def startTagFrameset(self, token):
- self.tree.insertElement(token)
-
- def startTagFrame(self, token):
- self.tree.insertElement(token)
- self.tree.openElements.pop()
-
- def startTagNoframes(self, token):
- return self.parser.phases["inBody"].processStartTag(token)
-
- def startTagOther(self, token):
- self.parser.parseError("unexpected-start-tag-in-frameset",
- {"name": token["name"]})
-
- def endTagFrameset(self, token):
- if self.tree.openElements[-1].name == "html":
- # innerHTML case
- self.parser.parseError("unexpected-frameset-in-frameset-innerhtml")
- else:
- self.tree.openElements.pop()
- if (not self.parser.innerHTML and
- self.tree.openElements[-1].name != "frameset"):
- # If we're not in innerHTML mode and the current node is not a
- # "frameset" element (anymore) then switch.
- self.parser.phase = self.parser.phases["afterFrameset"]
-
- def endTagOther(self, token):
- self.parser.parseError("unexpected-end-tag-in-frameset",
- {"name": token["name"]})
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", Phase.startTagHtml),
- ("frameset", startTagFrameset),
- ("frame", startTagFrame),
- ("noframes", startTagNoframes)
- ])
- startTagHandler.default = startTagOther
-
- endTagHandler = _utils.MethodDispatcher([
- ("frameset", endTagFrameset)
- ])
- endTagHandler.default = endTagOther
-
- class AfterFramesetPhase(Phase):
- # http://www.whatwg.org/specs/web-apps/current-work/#after3
- __slots__ = tuple()
-
- def processEOF(self):
- # Stop parsing
- pass
-
- def processCharacters(self, token):
- self.parser.parseError("unexpected-char-after-frameset")
-
- def startTagNoframes(self, token):
- return self.parser.phases["inHead"].processStartTag(token)
-
- def startTagOther(self, token):
- self.parser.parseError("unexpected-start-tag-after-frameset",
- {"name": token["name"]})
-
- def endTagHtml(self, token):
- self.parser.phase = self.parser.phases["afterAfterFrameset"]
-
- def endTagOther(self, token):
- self.parser.parseError("unexpected-end-tag-after-frameset",
- {"name": token["name"]})
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", Phase.startTagHtml),
- ("noframes", startTagNoframes)
- ])
- startTagHandler.default = startTagOther
-
- endTagHandler = _utils.MethodDispatcher([
- ("html", endTagHtml)
- ])
- endTagHandler.default = endTagOther
-
- class AfterAfterBodyPhase(Phase):
- __slots__ = tuple()
-
- def processEOF(self):
- pass
-
- def processComment(self, token):
- self.tree.insertComment(token, self.tree.document)
-
- def processSpaceCharacters(self, token):
- return self.parser.phases["inBody"].processSpaceCharacters(token)
-
- def processCharacters(self, token):
- self.parser.parseError("expected-eof-but-got-char")
- self.parser.phase = self.parser.phases["inBody"]
- return token
-
- def startTagHtml(self, token):
- return self.parser.phases["inBody"].processStartTag(token)
-
- def startTagOther(self, token):
- self.parser.parseError("expected-eof-but-got-start-tag",
- {"name": token["name"]})
- self.parser.phase = self.parser.phases["inBody"]
- return token
-
- def processEndTag(self, token):
- self.parser.parseError("expected-eof-but-got-end-tag",
- {"name": token["name"]})
- self.parser.phase = self.parser.phases["inBody"]
- return token
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", startTagHtml)
- ])
- startTagHandler.default = startTagOther
-
- class AfterAfterFramesetPhase(Phase):
- __slots__ = tuple()
-
- def processEOF(self):
- pass
-
- def processComment(self, token):
- self.tree.insertComment(token, self.tree.document)
-
- def processSpaceCharacters(self, token):
- return self.parser.phases["inBody"].processSpaceCharacters(token)
-
- def processCharacters(self, token):
- self.parser.parseError("expected-eof-but-got-char")
-
- def startTagHtml(self, token):
- return self.parser.phases["inBody"].processStartTag(token)
-
- def startTagNoFrames(self, token):
- return self.parser.phases["inHead"].processStartTag(token)
-
- def startTagOther(self, token):
- self.parser.parseError("expected-eof-but-got-start-tag",
- {"name": token["name"]})
-
- def processEndTag(self, token):
- self.parser.parseError("expected-eof-but-got-end-tag",
- {"name": token["name"]})
-
- startTagHandler = _utils.MethodDispatcher([
- ("html", startTagHtml),
- ("noframes", startTagNoFrames)
- ])
- startTagHandler.default = startTagOther
-
- # pylint:enable=unused-argument
-
- return {
- "initial": InitialPhase,
- "beforeHtml": BeforeHtmlPhase,
- "beforeHead": BeforeHeadPhase,
- "inHead": InHeadPhase,
- "inHeadNoscript": InHeadNoscriptPhase,
- "afterHead": AfterHeadPhase,
- "inBody": InBodyPhase,
- "text": TextPhase,
- "inTable": InTablePhase,
- "inTableText": InTableTextPhase,
- "inCaption": InCaptionPhase,
- "inColumnGroup": InColumnGroupPhase,
- "inTableBody": InTableBodyPhase,
- "inRow": InRowPhase,
- "inCell": InCellPhase,
- "inSelect": InSelectPhase,
- "inSelectInTable": InSelectInTablePhase,
- "inForeignContent": InForeignContentPhase,
- "afterBody": AfterBodyPhase,
- "inFrameset": InFramesetPhase,
- "afterFrameset": AfterFramesetPhase,
- "afterAfterBody": AfterAfterBodyPhase,
- "afterAfterFrameset": AfterAfterFramesetPhase,
- # XXX after after frameset
- }
-
-
-def adjust_attributes(token, replacements):
- needs_adjustment = viewkeys(token['data']) & viewkeys(replacements)
- if needs_adjustment:
- token['data'] = type(token['data'])((replacements.get(k, k), v)
- for k, v in token['data'].items())
-
-
-def impliedTagToken(name, type="EndTag", attributes=None,
- selfClosing=False):
- if attributes is None:
- attributes = {}
- return {"type": tokenTypes[type], "name": name, "data": attributes,
- "selfClosing": selfClosing}
-
-
-class ParseError(Exception):
- """Error in parsed document"""
- pass
diff --git a/src/pip/_vendor/html5lib/serializer.py b/src/pip/_vendor/html5lib/serializer.py
deleted file mode 100644
index d5669d8c1..000000000
--- a/src/pip/_vendor/html5lib/serializer.py
+++ /dev/null
@@ -1,409 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-from pip._vendor.six import text_type
-
-import re
-
-from codecs import register_error, xmlcharrefreplace_errors
-
-from .constants import voidElements, booleanAttributes, spaceCharacters
-from .constants import rcdataElements, entities, xmlEntities
-from . import treewalkers, _utils
-from xml.sax.saxutils import escape
-
-_quoteAttributeSpecChars = "".join(spaceCharacters) + "\"'=<>`"
-_quoteAttributeSpec = re.compile("[" + _quoteAttributeSpecChars + "]")
-_quoteAttributeLegacy = re.compile("[" + _quoteAttributeSpecChars +
- "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n"
- "\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15"
- "\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
- "\x20\x2f\x60\xa0\u1680\u180e\u180f\u2000"
- "\u2001\u2002\u2003\u2004\u2005\u2006\u2007"
- "\u2008\u2009\u200a\u2028\u2029\u202f\u205f"
- "\u3000]")
-
-
-_encode_entity_map = {}
-_is_ucs4 = len("\U0010FFFF") == 1
-for k, v in list(entities.items()):
- # skip multi-character entities
- if ((_is_ucs4 and len(v) > 1) or
- (not _is_ucs4 and len(v) > 2)):
- continue
- if v != "&":
- if len(v) == 2:
- v = _utils.surrogatePairToCodepoint(v)
- else:
- v = ord(v)
- if v not in _encode_entity_map or k.islower():
- # prefer &lt; over &LT; and similarly for &amp;, &gt;, etc.
- _encode_entity_map[v] = k
-
-
-def htmlentityreplace_errors(exc):
- if isinstance(exc, (UnicodeEncodeError, UnicodeTranslateError)):
- res = []
- codepoints = []
- skip = False
- for i, c in enumerate(exc.object[exc.start:exc.end]):
- if skip:
- skip = False
- continue
- index = i + exc.start
- if _utils.isSurrogatePair(exc.object[index:min([exc.end, index + 2])]):
- codepoint = _utils.surrogatePairToCodepoint(exc.object[index:index + 2])
- skip = True
- else:
- codepoint = ord(c)
- codepoints.append(codepoint)
- for cp in codepoints:
- e = _encode_entity_map.get(cp)
- if e:
- res.append("&")
- res.append(e)
- if not e.endswith(";"):
- res.append(";")
- else:
- res.append("&#x%s;" % (hex(cp)[2:]))
- return ("".join(res), exc.end)
- else:
- return xmlcharrefreplace_errors(exc)
-
-
-register_error("htmlentityreplace", htmlentityreplace_errors)
-
-
-def serialize(input, tree="etree", encoding=None, **serializer_opts):
- """Serializes the input token stream using the specified treewalker
-
- :arg input: the token stream to serialize
-
- :arg tree: the treewalker to use
-
- :arg encoding: the encoding to use
-
- :arg serializer_opts: any options to pass to the
- :py:class:`html5lib.serializer.HTMLSerializer` that gets created
-
- :returns: the tree serialized as a string
-
- Example:
-
- >>> from html5lib.html5parser import parse
- >>> from html5lib.serializer import serialize
- >>> token_stream = parse('<html><body><p>Hi!</p></body></html>')
- >>> serialize(token_stream, omit_optional_tags=False)
- '<html><head></head><body><p>Hi!</p></body></html>'
-
- """
- # XXX: Should we cache this?
- walker = treewalkers.getTreeWalker(tree)
- s = HTMLSerializer(**serializer_opts)
- return s.render(walker(input), encoding)
-
-
-class HTMLSerializer(object):
-
- # attribute quoting options
- quote_attr_values = "legacy" # be secure by default
- quote_char = '"'
- use_best_quote_char = True
-
- # tag syntax options
- omit_optional_tags = True
- minimize_boolean_attributes = True
- use_trailing_solidus = False
- space_before_trailing_solidus = True
-
- # escaping options
- escape_lt_in_attrs = False
- escape_rcdata = False
- resolve_entities = True
-
- # miscellaneous options
- alphabetical_attributes = False
- inject_meta_charset = True
- strip_whitespace = False
- sanitize = False
-
- options = ("quote_attr_values", "quote_char", "use_best_quote_char",
- "omit_optional_tags", "minimize_boolean_attributes",
- "use_trailing_solidus", "space_before_trailing_solidus",
- "escape_lt_in_attrs", "escape_rcdata", "resolve_entities",
- "alphabetical_attributes", "inject_meta_charset",
- "strip_whitespace", "sanitize")
-
- def __init__(self, **kwargs):
- """Initialize HTMLSerializer
-
- :arg inject_meta_charset: Whether or not to inject the meta charset.
-
- Defaults to ``True``.
-
- :arg quote_attr_values: Whether to quote attribute values that don't
- require quoting per legacy browser behavior (``"legacy"``), when
- required by the standard (``"spec"``), or always (``"always"``).
-
- Defaults to ``"legacy"``.
-
- :arg quote_char: Use given quote character for attribute quoting.
-
- Defaults to ``"`` which will use double quotes unless attribute
- value contains a double quote, in which case single quotes are
- used.
-
- :arg escape_lt_in_attrs: Whether or not to escape ``<`` in attribute
- values.
-
- Defaults to ``False``.
-
- :arg escape_rcdata: Whether to escape characters that need to be
- escaped within normal elements within rcdata elements such as
- style.
-
- Defaults to ``False``.
-
- :arg resolve_entities: Whether to resolve named character entities that
- appear in the source tree. The XML predefined entities &lt; &gt;
- &amp; &quot; &apos; are unaffected by this setting.
-
- Defaults to ``True``.
-
- :arg strip_whitespace: Whether to remove semantically meaningless
- whitespace. (This compresses all whitespace to a single space
- except within ``pre``.)
-
- Defaults to ``False``.
-
- :arg minimize_boolean_attributes: Shortens boolean attributes to give
- just the attribute value, for example::
-
- <input disabled="disabled">
-
- becomes::
-
- <input disabled>
-
- Defaults to ``True``.
-
- :arg use_trailing_solidus: Includes a close-tag slash at the end of the
- start tag of void elements (empty elements whose end tag is
- forbidden). E.g. ``<hr/>``.
-
- Defaults to ``False``.
-
- :arg space_before_trailing_solidus: Places a space immediately before
- the closing slash in a tag using a trailing solidus. E.g.
- ``<hr />``. Requires ``use_trailing_solidus=True``.
-
- Defaults to ``True``.
-
- :arg sanitize: Strip all unsafe or unknown constructs from output.
- See :py:class:`html5lib.filters.sanitizer.Filter`.
-
- Defaults to ``False``.
-
- :arg omit_optional_tags: Omit start/end tags that are optional.
-
- Defaults to ``True``.
-
- :arg alphabetical_attributes: Reorder attributes to be in alphabetical order.
-
- Defaults to ``False``.
-
- """
- unexpected_args = frozenset(kwargs) - frozenset(self.options)
- if len(unexpected_args) > 0:
- raise TypeError("__init__() got an unexpected keyword argument '%s'" % next(iter(unexpected_args)))
- if 'quote_char' in kwargs:
- self.use_best_quote_char = False
- for attr in self.options:
- setattr(self, attr, kwargs.get(attr, getattr(self, attr)))
- self.errors = []
- self.strict = False
-
- def encode(self, string):
- assert(isinstance(string, text_type))
- if self.encoding:
- return string.encode(self.encoding, "htmlentityreplace")
- else:
- return string
-
- def encodeStrict(self, string):
- assert(isinstance(string, text_type))
- if self.encoding:
- return string.encode(self.encoding, "strict")
- else:
- return string
-
- def serialize(self, treewalker, encoding=None):
- # pylint:disable=too-many-nested-blocks
- self.encoding = encoding
- in_cdata = False
- self.errors = []
-
- if encoding and self.inject_meta_charset:
- from .filters.inject_meta_charset import Filter
- treewalker = Filter(treewalker, encoding)
- # Alphabetical attributes is here under the assumption that none of
- # the later filters add or change order of attributes; it needs to be
- # before the sanitizer so escaped elements come out correctly
- if self.alphabetical_attributes:
- from .filters.alphabeticalattributes import Filter
- treewalker = Filter(treewalker)
- # WhitespaceFilter should be used before OptionalTagFilter
- # for maximum efficiently of this latter filter
- if self.strip_whitespace:
- from .filters.whitespace import Filter
- treewalker = Filter(treewalker)
- if self.sanitize:
- from .filters.sanitizer import Filter
- treewalker = Filter(treewalker)
- if self.omit_optional_tags:
- from .filters.optionaltags import Filter
- treewalker = Filter(treewalker)
-
- for token in treewalker:
- type = token["type"]
- if type == "Doctype":
- doctype = "<!DOCTYPE %s" % token["name"]
-
- if token["publicId"]:
- doctype += ' PUBLIC "%s"' % token["publicId"]
- elif token["systemId"]:
- doctype += " SYSTEM"
- if token["systemId"]:
- if token["systemId"].find('"') >= 0:
- if token["systemId"].find("'") >= 0:
- self.serializeError("System identifier contains both single and double quote characters")
- quote_char = "'"
- else:
- quote_char = '"'
- doctype += " %s%s%s" % (quote_char, token["systemId"], quote_char)
-
- doctype += ">"
- yield self.encodeStrict(doctype)
-
- elif type in ("Characters", "SpaceCharacters"):
- if type == "SpaceCharacters" or in_cdata:
- if in_cdata and token["data"].find("</") >= 0:
- self.serializeError("Unexpected </ in CDATA")
- yield self.encode(token["data"])
- else:
- yield self.encode(escape(token["data"]))
-
- elif type in ("StartTag", "EmptyTag"):
- name = token["name"]
- yield self.encodeStrict("<%s" % name)
- if name in rcdataElements and not self.escape_rcdata:
- in_cdata = True
- elif in_cdata:
- self.serializeError("Unexpected child element of a CDATA element")
- for (_, attr_name), attr_value in token["data"].items():
- # TODO: Add namespace support here
- k = attr_name
- v = attr_value
- yield self.encodeStrict(' ')
-
- yield self.encodeStrict(k)
- if not self.minimize_boolean_attributes or \
- (k not in booleanAttributes.get(name, tuple()) and
- k not in booleanAttributes.get("", tuple())):
- yield self.encodeStrict("=")
- if self.quote_attr_values == "always" or len(v) == 0:
- quote_attr = True
- elif self.quote_attr_values == "spec":
- quote_attr = _quoteAttributeSpec.search(v) is not None
- elif self.quote_attr_values == "legacy":
- quote_attr = _quoteAttributeLegacy.search(v) is not None
- else:
- raise ValueError("quote_attr_values must be one of: "
- "'always', 'spec', or 'legacy'")
- v = v.replace("&", "&amp;")
- if self.escape_lt_in_attrs:
- v = v.replace("<", "&lt;")
- if quote_attr:
- quote_char = self.quote_char
- if self.use_best_quote_char:
- if "'" in v and '"' not in v:
- quote_char = '"'
- elif '"' in v and "'" not in v:
- quote_char = "'"
- if quote_char == "'":
- v = v.replace("'", "&#39;")
- else:
- v = v.replace('"', "&quot;")
- yield self.encodeStrict(quote_char)
- yield self.encode(v)
- yield self.encodeStrict(quote_char)
- else:
- yield self.encode(v)
- if name in voidElements and self.use_trailing_solidus:
- if self.space_before_trailing_solidus:
- yield self.encodeStrict(" /")
- else:
- yield self.encodeStrict("/")
- yield self.encode(">")
-
- elif type == "EndTag":
- name = token["name"]
- if name in rcdataElements:
- in_cdata = False
- elif in_cdata:
- self.serializeError("Unexpected child element of a CDATA element")
- yield self.encodeStrict("</%s>" % name)
-
- elif type == "Comment":
- data = token["data"]
- if data.find("--") >= 0:
- self.serializeError("Comment contains --")
- yield self.encodeStrict("<!--%s-->" % token["data"])
-
- elif type == "Entity":
- name = token["name"]
- key = name + ";"
- if key not in entities:
- self.serializeError("Entity %s not recognized" % name)
- if self.resolve_entities and key not in xmlEntities:
- data = entities[key]
- else:
- data = "&%s;" % name
- yield self.encodeStrict(data)
-
- else:
- self.serializeError(token["data"])
-
- def render(self, treewalker, encoding=None):
- """Serializes the stream from the treewalker into a string
-
- :arg treewalker: the treewalker to serialize
-
- :arg encoding: the string encoding to use
-
- :returns: the serialized tree
-
- Example:
-
- >>> from html5lib import parse, getTreeWalker
- >>> from html5lib.serializer import HTMLSerializer
- >>> token_stream = parse('<html><body>Hi!</body></html>')
- >>> walker = getTreeWalker('etree')
- >>> serializer = HTMLSerializer(omit_optional_tags=False)
- >>> serializer.render(walker(token_stream))
- '<html><head></head><body>Hi!</body></html>'
-
- """
- if encoding:
- return b"".join(list(self.serialize(treewalker, encoding)))
- else:
- return "".join(list(self.serialize(treewalker)))
-
- def serializeError(self, data="XXX ERROR MESSAGE NEEDED"):
- # XXX The idea is to make data mandatory.
- self.errors.append(data)
- if self.strict:
- raise SerializeError
-
-
-class SerializeError(Exception):
- """Error in serialized tree"""
- pass
diff --git a/src/pip/_vendor/html5lib/treeadapters/__init__.py b/src/pip/_vendor/html5lib/treeadapters/__init__.py
deleted file mode 100644
index 7ef59590c..000000000
--- a/src/pip/_vendor/html5lib/treeadapters/__init__.py
+++ /dev/null
@@ -1,30 +0,0 @@
-"""Tree adapters let you convert from one tree structure to another
-
-Example:
-
-.. code-block:: python
-
- from pip._vendor import html5lib
- from pip._vendor.html5lib.treeadapters import genshi
-
- doc = '<html><body>Hi!</body></html>'
- treebuilder = html5lib.getTreeBuilder('etree')
- parser = html5lib.HTMLParser(tree=treebuilder)
- tree = parser.parse(doc)
- TreeWalker = html5lib.getTreeWalker('etree')
-
- genshi_tree = genshi.to_genshi(TreeWalker(tree))
-
-"""
-from __future__ import absolute_import, division, unicode_literals
-
-from . import sax
-
-__all__ = ["sax"]
-
-try:
- from . import genshi # noqa
-except ImportError:
- pass
-else:
- __all__.append("genshi")
diff --git a/src/pip/_vendor/html5lib/treeadapters/genshi.py b/src/pip/_vendor/html5lib/treeadapters/genshi.py
deleted file mode 100644
index 61d5fb6ac..000000000
--- a/src/pip/_vendor/html5lib/treeadapters/genshi.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-from genshi.core import QName, Attrs
-from genshi.core import START, END, TEXT, COMMENT, DOCTYPE
-
-
-def to_genshi(walker):
- """Convert a tree to a genshi tree
-
- :arg walker: the treewalker to use to walk the tree to convert it
-
- :returns: generator of genshi nodes
-
- """
- text = []
- for token in walker:
- type = token["type"]
- if type in ("Characters", "SpaceCharacters"):
- text.append(token["data"])
- elif text:
- yield TEXT, "".join(text), (None, -1, -1)
- text = []
-
- if type in ("StartTag", "EmptyTag"):
- if token["namespace"]:
- name = "{%s}%s" % (token["namespace"], token["name"])
- else:
- name = token["name"]
- attrs = Attrs([(QName("{%s}%s" % attr if attr[0] is not None else attr[1]), value)
- for attr, value in token["data"].items()])
- yield (START, (QName(name), attrs), (None, -1, -1))
- if type == "EmptyTag":
- type = "EndTag"
-
- if type == "EndTag":
- if token["namespace"]:
- name = "{%s}%s" % (token["namespace"], token["name"])
- else:
- name = token["name"]
-
- yield END, QName(name), (None, -1, -1)
-
- elif type == "Comment":
- yield COMMENT, token["data"], (None, -1, -1)
-
- elif type == "Doctype":
- yield DOCTYPE, (token["name"], token["publicId"],
- token["systemId"]), (None, -1, -1)
-
- else:
- pass # FIXME: What to do?
-
- if text:
- yield TEXT, "".join(text), (None, -1, -1)
diff --git a/src/pip/_vendor/html5lib/treeadapters/sax.py b/src/pip/_vendor/html5lib/treeadapters/sax.py
deleted file mode 100644
index f4ccea5a2..000000000
--- a/src/pip/_vendor/html5lib/treeadapters/sax.py
+++ /dev/null
@@ -1,50 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-from xml.sax.xmlreader import AttributesNSImpl
-
-from ..constants import adjustForeignAttributes, unadjustForeignAttributes
-
-prefix_mapping = {}
-for prefix, localName, namespace in adjustForeignAttributes.values():
- if prefix is not None:
- prefix_mapping[prefix] = namespace
-
-
-def to_sax(walker, handler):
- """Call SAX-like content handler based on treewalker walker
-
- :arg walker: the treewalker to use to walk the tree to convert it
-
- :arg handler: SAX handler to use
-
- """
- handler.startDocument()
- for prefix, namespace in prefix_mapping.items():
- handler.startPrefixMapping(prefix, namespace)
-
- for token in walker:
- type = token["type"]
- if type == "Doctype":
- continue
- elif type in ("StartTag", "EmptyTag"):
- attrs = AttributesNSImpl(token["data"],
- unadjustForeignAttributes)
- handler.startElementNS((token["namespace"], token["name"]),
- token["name"],
- attrs)
- if type == "EmptyTag":
- handler.endElementNS((token["namespace"], token["name"]),
- token["name"])
- elif type == "EndTag":
- handler.endElementNS((token["namespace"], token["name"]),
- token["name"])
- elif type in ("Characters", "SpaceCharacters"):
- handler.characters(token["data"])
- elif type == "Comment":
- pass
- else:
- assert False, "Unknown token type"
-
- for prefix, namespace in prefix_mapping.items():
- handler.endPrefixMapping(prefix)
- handler.endDocument()
diff --git a/src/pip/_vendor/html5lib/treebuilders/__init__.py b/src/pip/_vendor/html5lib/treebuilders/__init__.py
deleted file mode 100644
index d44447eaf..000000000
--- a/src/pip/_vendor/html5lib/treebuilders/__init__.py
+++ /dev/null
@@ -1,88 +0,0 @@
-"""A collection of modules for building different kinds of trees from HTML
-documents.
-
-To create a treebuilder for a new type of tree, you need to do
-implement several things:
-
-1. A set of classes for various types of elements: Document, Doctype, Comment,
- Element. These must implement the interface of ``base.treebuilders.Node``
- (although comment nodes have a different signature for their constructor,
- see ``treebuilders.etree.Comment``) Textual content may also be implemented
- as another node type, or not, as your tree implementation requires.
-
-2. A treebuilder object (called ``TreeBuilder`` by convention) that inherits
- from ``treebuilders.base.TreeBuilder``. This has 4 required attributes:
-
- * ``documentClass`` - the class to use for the bottommost node of a document
- * ``elementClass`` - the class to use for HTML Elements
- * ``commentClass`` - the class to use for comments
- * ``doctypeClass`` - the class to use for doctypes
-
- It also has one required method:
-
- * ``getDocument`` - Returns the root node of the complete document tree
-
-3. If you wish to run the unit tests, you must also create a ``testSerializer``
- method on your treebuilder which accepts a node and returns a string
- containing Node and its children serialized according to the format used in
- the unittests
-
-"""
-
-from __future__ import absolute_import, division, unicode_literals
-
-from .._utils import default_etree
-
-treeBuilderCache = {}
-
-
-def getTreeBuilder(treeType, implementation=None, **kwargs):
- """Get a TreeBuilder class for various types of trees with built-in support
-
- :arg treeType: the name of the tree type required (case-insensitive). Supported
- values are:
-
- * "dom" - A generic builder for DOM implementations, defaulting to a
- xml.dom.minidom based implementation.
- * "etree" - A generic builder for tree implementations exposing an
- ElementTree-like interface, defaulting to xml.etree.cElementTree if
- available and xml.etree.ElementTree if not.
- * "lxml" - A etree-based builder for lxml.etree, handling limitations
- of lxml's implementation.
-
- :arg implementation: (Currently applies to the "etree" and "dom" tree
- types). A module implementing the tree type e.g. xml.etree.ElementTree
- or xml.etree.cElementTree.
-
- :arg kwargs: Any additional options to pass to the TreeBuilder when
- creating it.
-
- Example:
-
- >>> from html5lib.treebuilders import getTreeBuilder
- >>> builder = getTreeBuilder('etree')
-
- """
-
- treeType = treeType.lower()
- if treeType not in treeBuilderCache:
- if treeType == "dom":
- from . import dom
- # Come up with a sane default (pref. from the stdlib)
- if implementation is None:
- from xml.dom import minidom
- implementation = minidom
- # NEVER cache here, caching is done in the dom submodule
- return dom.getDomModule(implementation, **kwargs).TreeBuilder
- elif treeType == "lxml":
- from . import etree_lxml
- treeBuilderCache[treeType] = etree_lxml.TreeBuilder
- elif treeType == "etree":
- from . import etree
- if implementation is None:
- implementation = default_etree
- # NEVER cache here, caching is done in the etree submodule
- return etree.getETreeModule(implementation, **kwargs).TreeBuilder
- else:
- raise ValueError("""Unrecognised treebuilder "%s" """ % treeType)
- return treeBuilderCache.get(treeType)
diff --git a/src/pip/_vendor/html5lib/treebuilders/base.py b/src/pip/_vendor/html5lib/treebuilders/base.py
deleted file mode 100644
index 965fce29d..000000000
--- a/src/pip/_vendor/html5lib/treebuilders/base.py
+++ /dev/null
@@ -1,417 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-from pip._vendor.six import text_type
-
-from ..constants import scopingElements, tableInsertModeElements, namespaces
-
-# The scope markers are inserted when entering object elements,
-# marquees, table cells, and table captions, and are used to prevent formatting
-# from "leaking" into tables, object elements, and marquees.
-Marker = None
-
-listElementsMap = {
- None: (frozenset(scopingElements), False),
- "button": (frozenset(scopingElements | {(namespaces["html"], "button")}), False),
- "list": (frozenset(scopingElements | {(namespaces["html"], "ol"),
- (namespaces["html"], "ul")}), False),
- "table": (frozenset([(namespaces["html"], "html"),
- (namespaces["html"], "table")]), False),
- "select": (frozenset([(namespaces["html"], "optgroup"),
- (namespaces["html"], "option")]), True)
-}
-
-
-class Node(object):
- """Represents an item in the tree"""
- def __init__(self, name):
- """Creates a Node
-
- :arg name: The tag name associated with the node
-
- """
- # The tag name associated with the node
- self.name = name
- # The parent of the current node (or None for the document node)
- self.parent = None
- # The value of the current node (applies to text nodes and comments)
- self.value = None
- # A dict holding name -> value pairs for attributes of the node
- self.attributes = {}
- # A list of child nodes of the current node. This must include all
- # elements but not necessarily other node types.
- self.childNodes = []
- # A list of miscellaneous flags that can be set on the node.
- self._flags = []
-
- def __str__(self):
- attributesStr = " ".join(["%s=\"%s\"" % (name, value)
- for name, value in
- self.attributes.items()])
- if attributesStr:
- return "<%s %s>" % (self.name, attributesStr)
- else:
- return "<%s>" % (self.name)
-
- def __repr__(self):
- return "<%s>" % (self.name)
-
- def appendChild(self, node):
- """Insert node as a child of the current node
-
- :arg node: the node to insert
-
- """
- raise NotImplementedError
-
- def insertText(self, data, insertBefore=None):
- """Insert data as text in the current node, positioned before the
- start of node insertBefore or to the end of the node's text.
-
- :arg data: the data to insert
-
- :arg insertBefore: True if you want to insert the text before the node
- and False if you want to insert it after the node
-
- """
- raise NotImplementedError
-
- def insertBefore(self, node, refNode):
- """Insert node as a child of the current node, before refNode in the
- list of child nodes. Raises ValueError if refNode is not a child of
- the current node
-
- :arg node: the node to insert
-
- :arg refNode: the child node to insert the node before
-
- """
- raise NotImplementedError
-
- def removeChild(self, node):
- """Remove node from the children of the current node
-
- :arg node: the child node to remove
-
- """
- raise NotImplementedError
-
- def reparentChildren(self, newParent):
- """Move all the children of the current node to newParent.
- This is needed so that trees that don't store text as nodes move the
- text in the correct way
-
- :arg newParent: the node to move all this node's children to
-
- """
- # XXX - should this method be made more general?
- for child in self.childNodes:
- newParent.appendChild(child)
- self.childNodes = []
-
- def cloneNode(self):
- """Return a shallow copy of the current node i.e. a node with the same
- name and attributes but with no parent or child nodes
- """
- raise NotImplementedError
-
- def hasContent(self):
- """Return true if the node has children or text, false otherwise
- """
- raise NotImplementedError
-
-
-class ActiveFormattingElements(list):
- def append(self, node):
- equalCount = 0
- if node != Marker:
- for element in self[::-1]:
- if element == Marker:
- break
- if self.nodesEqual(element, node):
- equalCount += 1
- if equalCount == 3:
- self.remove(element)
- break
- list.append(self, node)
-
- def nodesEqual(self, node1, node2):
- if not node1.nameTuple == node2.nameTuple:
- return False
-
- if not node1.attributes == node2.attributes:
- return False
-
- return True
-
-
-class TreeBuilder(object):
- """Base treebuilder implementation
-
- * documentClass - the class to use for the bottommost node of a document
- * elementClass - the class to use for HTML Elements
- * commentClass - the class to use for comments
- * doctypeClass - the class to use for doctypes
-
- """
- # pylint:disable=not-callable
-
- # Document class
- documentClass = None
-
- # The class to use for creating a node
- elementClass = None
-
- # The class to use for creating comments
- commentClass = None
-
- # The class to use for creating doctypes
- doctypeClass = None
-
- # Fragment class
- fragmentClass = None
-
- def __init__(self, namespaceHTMLElements):
- """Create a TreeBuilder
-
- :arg namespaceHTMLElements: whether or not to namespace HTML elements
-
- """
- if namespaceHTMLElements:
- self.defaultNamespace = "http://www.w3.org/1999/xhtml"
- else:
- self.defaultNamespace = None
- self.reset()
-
- def reset(self):
- self.openElements = []
- self.activeFormattingElements = ActiveFormattingElements()
-
- # XXX - rename these to headElement, formElement
- self.headPointer = None
- self.formPointer = None
-
- self.insertFromTable = False
-
- self.document = self.documentClass()
-
- def elementInScope(self, target, variant=None):
-
- # If we pass a node in we match that. if we pass a string
- # match any node with that name
- exactNode = hasattr(target, "nameTuple")
- if not exactNode:
- if isinstance(target, text_type):
- target = (namespaces["html"], target)
- assert isinstance(target, tuple)
-
- listElements, invert = listElementsMap[variant]
-
- for node in reversed(self.openElements):
- if exactNode and node == target:
- return True
- elif not exactNode and node.nameTuple == target:
- return True
- elif (invert ^ (node.nameTuple in listElements)):
- return False
-
- assert False # We should never reach this point
-
- def reconstructActiveFormattingElements(self):
- # Within this algorithm the order of steps described in the
- # specification is not quite the same as the order of steps in the
- # code. It should still do the same though.
-
- # Step 1: stop the algorithm when there's nothing to do.
- if not self.activeFormattingElements:
- return
-
- # Step 2 and step 3: we start with the last element. So i is -1.
- i = len(self.activeFormattingElements) - 1
- entry = self.activeFormattingElements[i]
- if entry == Marker or entry in self.openElements:
- return
-
- # Step 6
- while entry != Marker and entry not in self.openElements:
- if i == 0:
- # This will be reset to 0 below
- i = -1
- break
- i -= 1
- # Step 5: let entry be one earlier in the list.
- entry = self.activeFormattingElements[i]
-
- while True:
- # Step 7
- i += 1
-
- # Step 8
- entry = self.activeFormattingElements[i]
- clone = entry.cloneNode() # Mainly to get a new copy of the attributes
-
- # Step 9
- element = self.insertElement({"type": "StartTag",
- "name": clone.name,
- "namespace": clone.namespace,
- "data": clone.attributes})
-
- # Step 10
- self.activeFormattingElements[i] = element
-
- # Step 11
- if element == self.activeFormattingElements[-1]:
- break
-
- def clearActiveFormattingElements(self):
- entry = self.activeFormattingElements.pop()
- while self.activeFormattingElements and entry != Marker:
- entry = self.activeFormattingElements.pop()
-
- def elementInActiveFormattingElements(self, name):
- """Check if an element exists between the end of the active
- formatting elements and the last marker. If it does, return it, else
- return false"""
-
- for item in self.activeFormattingElements[::-1]:
- # Check for Marker first because if it's a Marker it doesn't have a
- # name attribute.
- if item == Marker:
- break
- elif item.name == name:
- return item
- return False
-
- def insertRoot(self, token):
- element = self.createElement(token)
- self.openElements.append(element)
- self.document.appendChild(element)
-
- def insertDoctype(self, token):
- name = token["name"]
- publicId = token["publicId"]
- systemId = token["systemId"]
-
- doctype = self.doctypeClass(name, publicId, systemId)
- self.document.appendChild(doctype)
-
- def insertComment(self, token, parent=None):
- if parent is None:
- parent = self.openElements[-1]
- parent.appendChild(self.commentClass(token["data"]))
-
- def createElement(self, token):
- """Create an element but don't insert it anywhere"""
- name = token["name"]
- namespace = token.get("namespace", self.defaultNamespace)
- element = self.elementClass(name, namespace)
- element.attributes = token["data"]
- return element
-
- def _getInsertFromTable(self):
- return self._insertFromTable
-
- def _setInsertFromTable(self, value):
- """Switch the function used to insert an element from the
- normal one to the misnested table one and back again"""
- self._insertFromTable = value
- if value:
- self.insertElement = self.insertElementTable
- else:
- self.insertElement = self.insertElementNormal
-
- insertFromTable = property(_getInsertFromTable, _setInsertFromTable)
-
- def insertElementNormal(self, token):
- name = token["name"]
- assert isinstance(name, text_type), "Element %s not unicode" % name
- namespace = token.get("namespace", self.defaultNamespace)
- element = self.elementClass(name, namespace)
- element.attributes = token["data"]
- self.openElements[-1].appendChild(element)
- self.openElements.append(element)
- return element
-
- def insertElementTable(self, token):
- """Create an element and insert it into the tree"""
- element = self.createElement(token)
- if self.openElements[-1].name not in tableInsertModeElements:
- return self.insertElementNormal(token)
- else:
- # We should be in the InTable mode. This means we want to do
- # special magic element rearranging
- parent, insertBefore = self.getTableMisnestedNodePosition()
- if insertBefore is None:
- parent.appendChild(element)
- else:
- parent.insertBefore(element, insertBefore)
- self.openElements.append(element)
- return element
-
- def insertText(self, data, parent=None):
- """Insert text data."""
- if parent is None:
- parent = self.openElements[-1]
-
- if (not self.insertFromTable or (self.insertFromTable and
- self.openElements[-1].name
- not in tableInsertModeElements)):
- parent.insertText(data)
- else:
- # We should be in the InTable mode. This means we want to do
- # special magic element rearranging
- parent, insertBefore = self.getTableMisnestedNodePosition()
- parent.insertText(data, insertBefore)
-
- def getTableMisnestedNodePosition(self):
- """Get the foster parent element, and sibling to insert before
- (or None) when inserting a misnested table node"""
- # The foster parent element is the one which comes before the most
- # recently opened table element
- # XXX - this is really inelegant
- lastTable = None
- fosterParent = None
- insertBefore = None
- for elm in self.openElements[::-1]:
- if elm.name == "table":
- lastTable = elm
- break
- if lastTable:
- # XXX - we should really check that this parent is actually a
- # node here
- if lastTable.parent:
- fosterParent = lastTable.parent
- insertBefore = lastTable
- else:
- fosterParent = self.openElements[
- self.openElements.index(lastTable) - 1]
- else:
- fosterParent = self.openElements[0]
- return fosterParent, insertBefore
-
- def generateImpliedEndTags(self, exclude=None):
- name = self.openElements[-1].name
- # XXX td, th and tr are not actually needed
- if (name in frozenset(("dd", "dt", "li", "option", "optgroup", "p", "rp", "rt")) and
- name != exclude):
- self.openElements.pop()
- # XXX This is not entirely what the specification says. We should
- # investigate it more closely.
- self.generateImpliedEndTags(exclude)
-
- def getDocument(self):
- """Return the final tree"""
- return self.document
-
- def getFragment(self):
- """Return the final fragment"""
- # assert self.innerHTML
- fragment = self.fragmentClass()
- self.openElements[0].reparentChildren(fragment)
- return fragment
-
- def testSerializer(self, node):
- """Serialize the subtree of node in the format required by unit tests
-
- :arg node: the node from which to start serializing
-
- """
- raise NotImplementedError
diff --git a/src/pip/_vendor/html5lib/treebuilders/dom.py b/src/pip/_vendor/html5lib/treebuilders/dom.py
deleted file mode 100644
index d8b530046..000000000
--- a/src/pip/_vendor/html5lib/treebuilders/dom.py
+++ /dev/null
@@ -1,239 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-
-try:
- from collections.abc import MutableMapping
-except ImportError: # Python 2.7
- from collections import MutableMapping
-from xml.dom import minidom, Node
-import weakref
-
-from . import base
-from .. import constants
-from ..constants import namespaces
-from .._utils import moduleFactoryFactory
-
-
-def getDomBuilder(DomImplementation):
- Dom = DomImplementation
-
- class AttrList(MutableMapping):
- def __init__(self, element):
- self.element = element
-
- def __iter__(self):
- return iter(self.element.attributes.keys())
-
- def __setitem__(self, name, value):
- if isinstance(name, tuple):
- raise NotImplementedError
- else:
- attr = self.element.ownerDocument.createAttribute(name)
- attr.value = value
- self.element.attributes[name] = attr
-
- def __len__(self):
- return len(self.element.attributes)
-
- def items(self):
- return list(self.element.attributes.items())
-
- def values(self):
- return list(self.element.attributes.values())
-
- def __getitem__(self, name):
- if isinstance(name, tuple):
- raise NotImplementedError
- else:
- return self.element.attributes[name].value
-
- def __delitem__(self, name):
- if isinstance(name, tuple):
- raise NotImplementedError
- else:
- del self.element.attributes[name]
-
- class NodeBuilder(base.Node):
- def __init__(self, element):
- base.Node.__init__(self, element.nodeName)
- self.element = element
-
- namespace = property(lambda self: hasattr(self.element, "namespaceURI") and
- self.element.namespaceURI or None)
-
- def appendChild(self, node):
- node.parent = self
- self.element.appendChild(node.element)
-
- def insertText(self, data, insertBefore=None):
- text = self.element.ownerDocument.createTextNode(data)
- if insertBefore:
- self.element.insertBefore(text, insertBefore.element)
- else:
- self.element.appendChild(text)
-
- def insertBefore(self, node, refNode):
- self.element.insertBefore(node.element, refNode.element)
- node.parent = self
-
- def removeChild(self, node):
- if node.element.parentNode == self.element:
- self.element.removeChild(node.element)
- node.parent = None
-
- def reparentChildren(self, newParent):
- while self.element.hasChildNodes():
- child = self.element.firstChild
- self.element.removeChild(child)
- newParent.element.appendChild(child)
- self.childNodes = []
-
- def getAttributes(self):
- return AttrList(self.element)
-
- def setAttributes(self, attributes):
- if attributes:
- for name, value in list(attributes.items()):
- if isinstance(name, tuple):
- if name[0] is not None:
- qualifiedName = (name[0] + ":" + name[1])
- else:
- qualifiedName = name[1]
- self.element.setAttributeNS(name[2], qualifiedName,
- value)
- else:
- self.element.setAttribute(
- name, value)
- attributes = property(getAttributes, setAttributes)
-
- def cloneNode(self):
- return NodeBuilder(self.element.cloneNode(False))
-
- def hasContent(self):
- return self.element.hasChildNodes()
-
- def getNameTuple(self):
- if self.namespace is None:
- return namespaces["html"], self.name
- else:
- return self.namespace, self.name
-
- nameTuple = property(getNameTuple)
-
- class TreeBuilder(base.TreeBuilder): # pylint:disable=unused-variable
- def documentClass(self):
- self.dom = Dom.getDOMImplementation().createDocument(None, None, None)
- return weakref.proxy(self)
-
- def insertDoctype(self, token):
- name = token["name"]
- publicId = token["publicId"]
- systemId = token["systemId"]
-
- domimpl = Dom.getDOMImplementation()
- doctype = domimpl.createDocumentType(name, publicId, systemId)
- self.document.appendChild(NodeBuilder(doctype))
- if Dom == minidom:
- doctype.ownerDocument = self.dom
-
- def elementClass(self, name, namespace=None):
- if namespace is None and self.defaultNamespace is None:
- node = self.dom.createElement(name)
- else:
- node = self.dom.createElementNS(namespace, name)
-
- return NodeBuilder(node)
-
- def commentClass(self, data):
- return NodeBuilder(self.dom.createComment(data))
-
- def fragmentClass(self):
- return NodeBuilder(self.dom.createDocumentFragment())
-
- def appendChild(self, node):
- self.dom.appendChild(node.element)
-
- def testSerializer(self, element):
- return testSerializer(element)
-
- def getDocument(self):
- return self.dom
-
- def getFragment(self):
- return base.TreeBuilder.getFragment(self).element
-
- def insertText(self, data, parent=None):
- data = data
- if parent != self:
- base.TreeBuilder.insertText(self, data, parent)
- else:
- # HACK: allow text nodes as children of the document node
- if hasattr(self.dom, '_child_node_types'):
- # pylint:disable=protected-access
- if Node.TEXT_NODE not in self.dom._child_node_types:
- self.dom._child_node_types = list(self.dom._child_node_types)
- self.dom._child_node_types.append(Node.TEXT_NODE)
- self.dom.appendChild(self.dom.createTextNode(data))
-
- implementation = DomImplementation
- name = None
-
- def testSerializer(element):
- element.normalize()
- rv = []
-
- def serializeElement(element, indent=0):
- if element.nodeType == Node.DOCUMENT_TYPE_NODE:
- if element.name:
- if element.publicId or element.systemId:
- publicId = element.publicId or ""
- systemId = element.systemId or ""
- rv.append("""|%s<!DOCTYPE %s "%s" "%s">""" %
- (' ' * indent, element.name, publicId, systemId))
- else:
- rv.append("|%s<!DOCTYPE %s>" % (' ' * indent, element.name))
- else:
- rv.append("|%s<!DOCTYPE >" % (' ' * indent,))
- elif element.nodeType == Node.DOCUMENT_NODE:
- rv.append("#document")
- elif element.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
- rv.append("#document-fragment")
- elif element.nodeType == Node.COMMENT_NODE:
- rv.append("|%s<!-- %s -->" % (' ' * indent, element.nodeValue))
- elif element.nodeType == Node.TEXT_NODE:
- rv.append("|%s\"%s\"" % (' ' * indent, element.nodeValue))
- else:
- if (hasattr(element, "namespaceURI") and
- element.namespaceURI is not None):
- name = "%s %s" % (constants.prefixes[element.namespaceURI],
- element.nodeName)
- else:
- name = element.nodeName
- rv.append("|%s<%s>" % (' ' * indent, name))
- if element.hasAttributes():
- attributes = []
- for i in range(len(element.attributes)):
- attr = element.attributes.item(i)
- name = attr.nodeName
- value = attr.value
- ns = attr.namespaceURI
- if ns:
- name = "%s %s" % (constants.prefixes[ns], attr.localName)
- else:
- name = attr.nodeName
- attributes.append((name, value))
-
- for name, value in sorted(attributes):
- rv.append('|%s%s="%s"' % (' ' * (indent + 2), name, value))
- indent += 2
- for child in element.childNodes:
- serializeElement(child, indent)
- serializeElement(element, 0)
-
- return "\n".join(rv)
-
- return locals()
-
-
-# The actual means to get a module!
-getDomModule = moduleFactoryFactory(getDomBuilder)
diff --git a/src/pip/_vendor/html5lib/treebuilders/etree.py b/src/pip/_vendor/html5lib/treebuilders/etree.py
deleted file mode 100644
index ea92dc301..000000000
--- a/src/pip/_vendor/html5lib/treebuilders/etree.py
+++ /dev/null
@@ -1,343 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-# pylint:disable=protected-access
-
-from pip._vendor.six import text_type
-
-import re
-
-from copy import copy
-
-from . import base
-from .. import _ihatexml
-from .. import constants
-from ..constants import namespaces
-from .._utils import moduleFactoryFactory
-
-tag_regexp = re.compile("{([^}]*)}(.*)")
-
-
-def getETreeBuilder(ElementTreeImplementation, fullTree=False):
- ElementTree = ElementTreeImplementation
- ElementTreeCommentType = ElementTree.Comment("asd").tag
-
- class Element(base.Node):
- def __init__(self, name, namespace=None):
- self._name = name
- self._namespace = namespace
- self._element = ElementTree.Element(self._getETreeTag(name,
- namespace))
- if namespace is None:
- self.nameTuple = namespaces["html"], self._name
- else:
- self.nameTuple = self._namespace, self._name
- self.parent = None
- self._childNodes = []
- self._flags = []
-
- def _getETreeTag(self, name, namespace):
- if namespace is None:
- etree_tag = name
- else:
- etree_tag = "{%s}%s" % (namespace, name)
- return etree_tag
-
- def _setName(self, name):
- self._name = name
- self._element.tag = self._getETreeTag(self._name, self._namespace)
-
- def _getName(self):
- return self._name
-
- name = property(_getName, _setName)
-
- def _setNamespace(self, namespace):
- self._namespace = namespace
- self._element.tag = self._getETreeTag(self._name, self._namespace)
-
- def _getNamespace(self):
- return self._namespace
-
- namespace = property(_getNamespace, _setNamespace)
-
- def _getAttributes(self):
- return self._element.attrib
-
- def _setAttributes(self, attributes):
- el_attrib = self._element.attrib
- el_attrib.clear()
- if attributes:
- # calling .items _always_ allocates, and the above truthy check is cheaper than the
- # allocation on average
- for key, value in attributes.items():
- if isinstance(key, tuple):
- name = "{%s}%s" % (key[2], key[1])
- else:
- name = key
- el_attrib[name] = value
-
- attributes = property(_getAttributes, _setAttributes)
-
- def _getChildNodes(self):
- return self._childNodes
-
- def _setChildNodes(self, value):
- del self._element[:]
- self._childNodes = []
- for element in value:
- self.insertChild(element)
-
- childNodes = property(_getChildNodes, _setChildNodes)
-
- def hasContent(self):
- """Return true if the node has children or text"""
- return bool(self._element.text or len(self._element))
-
- def appendChild(self, node):
- self._childNodes.append(node)
- self._element.append(node._element)
- node.parent = self
-
- def insertBefore(self, node, refNode):
- index = list(self._element).index(refNode._element)
- self._element.insert(index, node._element)
- node.parent = self
-
- def removeChild(self, node):
- self._childNodes.remove(node)
- self._element.remove(node._element)
- node.parent = None
-
- def insertText(self, data, insertBefore=None):
- if not(len(self._element)):
- if not self._element.text:
- self._element.text = ""
- self._element.text += data
- elif insertBefore is None:
- # Insert the text as the tail of the last child element
- if not self._element[-1].tail:
- self._element[-1].tail = ""
- self._element[-1].tail += data
- else:
- # Insert the text before the specified node
- children = list(self._element)
- index = children.index(insertBefore._element)
- if index > 0:
- if not self._element[index - 1].tail:
- self._element[index - 1].tail = ""
- self._element[index - 1].tail += data
- else:
- if not self._element.text:
- self._element.text = ""
- self._element.text += data
-
- def cloneNode(self):
- element = type(self)(self.name, self.namespace)
- if self._element.attrib:
- element._element.attrib = copy(self._element.attrib)
- return element
-
- def reparentChildren(self, newParent):
- if newParent.childNodes:
- newParent.childNodes[-1]._element.tail += self._element.text
- else:
- if not newParent._element.text:
- newParent._element.text = ""
- if self._element.text is not None:
- newParent._element.text += self._element.text
- self._element.text = ""
- base.Node.reparentChildren(self, newParent)
-
- class Comment(Element):
- def __init__(self, data):
- # Use the superclass constructor to set all properties on the
- # wrapper element
- self._element = ElementTree.Comment(data)
- self.parent = None
- self._childNodes = []
- self._flags = []
-
- def _getData(self):
- return self._element.text
-
- def _setData(self, value):
- self._element.text = value
-
- data = property(_getData, _setData)
-
- class DocumentType(Element):
- def __init__(self, name, publicId, systemId):
- Element.__init__(self, "<!DOCTYPE>")
- self._element.text = name
- self.publicId = publicId
- self.systemId = systemId
-
- def _getPublicId(self):
- return self._element.get("publicId", "")
-
- def _setPublicId(self, value):
- if value is not None:
- self._element.set("publicId", value)
-
- publicId = property(_getPublicId, _setPublicId)
-
- def _getSystemId(self):
- return self._element.get("systemId", "")
-
- def _setSystemId(self, value):
- if value is not None:
- self._element.set("systemId", value)
-
- systemId = property(_getSystemId, _setSystemId)
-
- class Document(Element):
- def __init__(self):
- Element.__init__(self, "DOCUMENT_ROOT")
-
- class DocumentFragment(Element):
- def __init__(self):
- Element.__init__(self, "DOCUMENT_FRAGMENT")
-
- def testSerializer(element):
- rv = []
-
- def serializeElement(element, indent=0):
- if not(hasattr(element, "tag")):
- element = element.getroot()
- if element.tag == "<!DOCTYPE>":
- if element.get("publicId") or element.get("systemId"):
- publicId = element.get("publicId") or ""
- systemId = element.get("systemId") or ""
- rv.append("""<!DOCTYPE %s "%s" "%s">""" %
- (element.text, publicId, systemId))
- else:
- rv.append("<!DOCTYPE %s>" % (element.text,))
- elif element.tag == "DOCUMENT_ROOT":
- rv.append("#document")
- if element.text is not None:
- rv.append("|%s\"%s\"" % (' ' * (indent + 2), element.text))
- if element.tail is not None:
- raise TypeError("Document node cannot have tail")
- if hasattr(element, "attrib") and len(element.attrib):
- raise TypeError("Document node cannot have attributes")
- elif element.tag == ElementTreeCommentType:
- rv.append("|%s<!-- %s -->" % (' ' * indent, element.text))
- else:
- assert isinstance(element.tag, text_type), \
- "Expected unicode, got %s, %s" % (type(element.tag), element.tag)
- nsmatch = tag_regexp.match(element.tag)
-
- if nsmatch is None:
- name = element.tag
- else:
- ns, name = nsmatch.groups()
- prefix = constants.prefixes[ns]
- name = "%s %s" % (prefix, name)
- rv.append("|%s<%s>" % (' ' * indent, name))
-
- if hasattr(element, "attrib"):
- attributes = []
- for name, value in element.attrib.items():
- nsmatch = tag_regexp.match(name)
- if nsmatch is not None:
- ns, name = nsmatch.groups()
- prefix = constants.prefixes[ns]
- attr_string = "%s %s" % (prefix, name)
- else:
- attr_string = name
- attributes.append((attr_string, value))
-
- for name, value in sorted(attributes):
- rv.append('|%s%s="%s"' % (' ' * (indent + 2), name, value))
- if element.text:
- rv.append("|%s\"%s\"" % (' ' * (indent + 2), element.text))
- indent += 2
- for child in element:
- serializeElement(child, indent)
- if element.tail:
- rv.append("|%s\"%s\"" % (' ' * (indent - 2), element.tail))
- serializeElement(element, 0)
-
- return "\n".join(rv)
-
- def tostring(element): # pylint:disable=unused-variable
- """Serialize an element and its child nodes to a string"""
- rv = []
- filter = _ihatexml.InfosetFilter()
-
- def serializeElement(element):
- if isinstance(element, ElementTree.ElementTree):
- element = element.getroot()
-
- if element.tag == "<!DOCTYPE>":
- if element.get("publicId") or element.get("systemId"):
- publicId = element.get("publicId") or ""
- systemId = element.get("systemId") or ""
- rv.append("""<!DOCTYPE %s PUBLIC "%s" "%s">""" %
- (element.text, publicId, systemId))
- else:
- rv.append("<!DOCTYPE %s>" % (element.text,))
- elif element.tag == "DOCUMENT_ROOT":
- if element.text is not None:
- rv.append(element.text)
- if element.tail is not None:
- raise TypeError("Document node cannot have tail")
- if hasattr(element, "attrib") and len(element.attrib):
- raise TypeError("Document node cannot have attributes")
-
- for child in element:
- serializeElement(child)
-
- elif element.tag == ElementTreeCommentType:
- rv.append("<!--%s-->" % (element.text,))
- else:
- # This is assumed to be an ordinary element
- if not element.attrib:
- rv.append("<%s>" % (filter.fromXmlName(element.tag),))
- else:
- attr = " ".join(["%s=\"%s\"" % (
- filter.fromXmlName(name), value)
- for name, value in element.attrib.items()])
- rv.append("<%s %s>" % (element.tag, attr))
- if element.text:
- rv.append(element.text)
-
- for child in element:
- serializeElement(child)
-
- rv.append("</%s>" % (element.tag,))
-
- if element.tail:
- rv.append(element.tail)
-
- serializeElement(element)
-
- return "".join(rv)
-
- class TreeBuilder(base.TreeBuilder): # pylint:disable=unused-variable
- documentClass = Document
- doctypeClass = DocumentType
- elementClass = Element
- commentClass = Comment
- fragmentClass = DocumentFragment
- implementation = ElementTreeImplementation
-
- def testSerializer(self, element):
- return testSerializer(element)
-
- def getDocument(self):
- if fullTree:
- return self.document._element
- else:
- if self.defaultNamespace is not None:
- return self.document._element.find(
- "{%s}html" % self.defaultNamespace)
- else:
- return self.document._element.find("html")
-
- def getFragment(self):
- return base.TreeBuilder.getFragment(self)._element
-
- return locals()
-
-
-getETreeModule = moduleFactoryFactory(getETreeBuilder)
diff --git a/src/pip/_vendor/html5lib/treebuilders/etree_lxml.py b/src/pip/_vendor/html5lib/treebuilders/etree_lxml.py
deleted file mode 100644
index f037759f4..000000000
--- a/src/pip/_vendor/html5lib/treebuilders/etree_lxml.py
+++ /dev/null
@@ -1,392 +0,0 @@
-"""Module for supporting the lxml.etree library. The idea here is to use as much
-of the native library as possible, without using fragile hacks like custom element
-names that break between releases. The downside of this is that we cannot represent
-all possible trees; specifically the following are known to cause problems:
-
-Text or comments as siblings of the root element
-Docypes with no name
-
-When any of these things occur, we emit a DataLossWarning
-"""
-
-from __future__ import absolute_import, division, unicode_literals
-# pylint:disable=protected-access
-
-import warnings
-import re
-import sys
-
-try:
- from collections.abc import MutableMapping
-except ImportError:
- from collections import MutableMapping
-
-from . import base
-from ..constants import DataLossWarning
-from .. import constants
-from . import etree as etree_builders
-from .. import _ihatexml
-
-import lxml.etree as etree
-from pip._vendor.six import PY3, binary_type
-
-
-fullTree = True
-tag_regexp = re.compile("{([^}]*)}(.*)")
-
-comment_type = etree.Comment("asd").tag
-
-
-class DocumentType(object):
- def __init__(self, name, publicId, systemId):
- self.name = name
- self.publicId = publicId
- self.systemId = systemId
-
-
-class Document(object):
- def __init__(self):
- self._elementTree = None
- self._childNodes = []
-
- def appendChild(self, element):
- last = self._elementTree.getroot()
- for last in self._elementTree.getroot().itersiblings():
- pass
-
- last.addnext(element._element)
-
- def _getChildNodes(self):
- return self._childNodes
-
- childNodes = property(_getChildNodes)
-
-
-def testSerializer(element):
- rv = []
- infosetFilter = _ihatexml.InfosetFilter(preventDoubleDashComments=True)
-
- def serializeElement(element, indent=0):
- if not hasattr(element, "tag"):
- if hasattr(element, "getroot"):
- # Full tree case
- rv.append("#document")
- if element.docinfo.internalDTD:
- if not (element.docinfo.public_id or
- element.docinfo.system_url):
- dtd_str = "<!DOCTYPE %s>" % element.docinfo.root_name
- else:
- dtd_str = """<!DOCTYPE %s "%s" "%s">""" % (
- element.docinfo.root_name,
- element.docinfo.public_id,
- element.docinfo.system_url)
- rv.append("|%s%s" % (' ' * (indent + 2), dtd_str))
- next_element = element.getroot()
- while next_element.getprevious() is not None:
- next_element = next_element.getprevious()
- while next_element is not None:
- serializeElement(next_element, indent + 2)
- next_element = next_element.getnext()
- elif isinstance(element, str) or isinstance(element, bytes):
- # Text in a fragment
- assert isinstance(element, str) or sys.version_info[0] == 2
- rv.append("|%s\"%s\"" % (' ' * indent, element))
- else:
- # Fragment case
- rv.append("#document-fragment")
- for next_element in element:
- serializeElement(next_element, indent + 2)
- elif element.tag == comment_type:
- rv.append("|%s<!-- %s -->" % (' ' * indent, element.text))
- if hasattr(element, "tail") and element.tail:
- rv.append("|%s\"%s\"" % (' ' * indent, element.tail))
- else:
- assert isinstance(element, etree._Element)
- nsmatch = etree_builders.tag_regexp.match(element.tag)
- if nsmatch is not None:
- ns = nsmatch.group(1)
- tag = nsmatch.group(2)
- prefix = constants.prefixes[ns]
- rv.append("|%s<%s %s>" % (' ' * indent, prefix,
- infosetFilter.fromXmlName(tag)))
- else:
- rv.append("|%s<%s>" % (' ' * indent,
- infosetFilter.fromXmlName(element.tag)))
-
- if hasattr(element, "attrib"):
- attributes = []
- for name, value in element.attrib.items():
- nsmatch = tag_regexp.match(name)
- if nsmatch is not None:
- ns, name = nsmatch.groups()
- name = infosetFilter.fromXmlName(name)
- prefix = constants.prefixes[ns]
- attr_string = "%s %s" % (prefix, name)
- else:
- attr_string = infosetFilter.fromXmlName(name)
- attributes.append((attr_string, value))
-
- for name, value in sorted(attributes):
- rv.append('|%s%s="%s"' % (' ' * (indent + 2), name, value))
-
- if element.text:
- rv.append("|%s\"%s\"" % (' ' * (indent + 2), element.text))
- indent += 2
- for child in element:
- serializeElement(child, indent)
- if hasattr(element, "tail") and element.tail:
- rv.append("|%s\"%s\"" % (' ' * (indent - 2), element.tail))
- serializeElement(element, 0)
-
- return "\n".join(rv)
-
-
-def tostring(element):
- """Serialize an element and its child nodes to a string"""
- rv = []
-
- def serializeElement(element):
- if not hasattr(element, "tag"):
- if element.docinfo.internalDTD:
- if element.docinfo.doctype:
- dtd_str = element.docinfo.doctype
- else:
- dtd_str = "<!DOCTYPE %s>" % element.docinfo.root_name
- rv.append(dtd_str)
- serializeElement(element.getroot())
-
- elif element.tag == comment_type:
- rv.append("<!--%s-->" % (element.text,))
-
- else:
- # This is assumed to be an ordinary element
- if not element.attrib:
- rv.append("<%s>" % (element.tag,))
- else:
- attr = " ".join(["%s=\"%s\"" % (name, value)
- for name, value in element.attrib.items()])
- rv.append("<%s %s>" % (element.tag, attr))
- if element.text:
- rv.append(element.text)
-
- for child in element:
- serializeElement(child)
-
- rv.append("</%s>" % (element.tag,))
-
- if hasattr(element, "tail") and element.tail:
- rv.append(element.tail)
-
- serializeElement(element)
-
- return "".join(rv)
-
-
-class TreeBuilder(base.TreeBuilder):
- documentClass = Document
- doctypeClass = DocumentType
- elementClass = None
- commentClass = None
- fragmentClass = Document
- implementation = etree
-
- def __init__(self, namespaceHTMLElements, fullTree=False):
- builder = etree_builders.getETreeModule(etree, fullTree=fullTree)
- infosetFilter = self.infosetFilter = _ihatexml.InfosetFilter(preventDoubleDashComments=True)
- self.namespaceHTMLElements = namespaceHTMLElements
-
- class Attributes(MutableMapping):
- def __init__(self, element):
- self._element = element
-
- def _coerceKey(self, key):
- if isinstance(key, tuple):
- name = "{%s}%s" % (key[2], infosetFilter.coerceAttribute(key[1]))
- else:
- name = infosetFilter.coerceAttribute(key)
- return name
-
- def __getitem__(self, key):
- value = self._element._element.attrib[self._coerceKey(key)]
- if not PY3 and isinstance(value, binary_type):
- value = value.decode("ascii")
- return value
-
- def __setitem__(self, key, value):
- self._element._element.attrib[self._coerceKey(key)] = value
-
- def __delitem__(self, key):
- del self._element._element.attrib[self._coerceKey(key)]
-
- def __iter__(self):
- return iter(self._element._element.attrib)
-
- def __len__(self):
- return len(self._element._element.attrib)
-
- def clear(self):
- return self._element._element.attrib.clear()
-
- class Element(builder.Element):
- def __init__(self, name, namespace):
- name = infosetFilter.coerceElement(name)
- builder.Element.__init__(self, name, namespace=namespace)
- self._attributes = Attributes(self)
-
- def _setName(self, name):
- self._name = infosetFilter.coerceElement(name)
- self._element.tag = self._getETreeTag(
- self._name, self._namespace)
-
- def _getName(self):
- return infosetFilter.fromXmlName(self._name)
-
- name = property(_getName, _setName)
-
- def _getAttributes(self):
- return self._attributes
-
- def _setAttributes(self, value):
- attributes = self.attributes
- attributes.clear()
- attributes.update(value)
-
- attributes = property(_getAttributes, _setAttributes)
-
- def insertText(self, data, insertBefore=None):
- data = infosetFilter.coerceCharacters(data)
- builder.Element.insertText(self, data, insertBefore)
-
- def cloneNode(self):
- element = type(self)(self.name, self.namespace)
- if self._element.attrib:
- element._element.attrib.update(self._element.attrib)
- return element
-
- class Comment(builder.Comment):
- def __init__(self, data):
- data = infosetFilter.coerceComment(data)
- builder.Comment.__init__(self, data)
-
- def _setData(self, data):
- data = infosetFilter.coerceComment(data)
- self._element.text = data
-
- def _getData(self):
- return self._element.text
-
- data = property(_getData, _setData)
-
- self.elementClass = Element
- self.commentClass = Comment
- # self.fragmentClass = builder.DocumentFragment
- base.TreeBuilder.__init__(self, namespaceHTMLElements)
-
- def reset(self):
- base.TreeBuilder.reset(self)
- self.insertComment = self.insertCommentInitial
- self.initial_comments = []
- self.doctype = None
-
- def testSerializer(self, element):
- return testSerializer(element)
-
- def getDocument(self):
- if fullTree:
- return self.document._elementTree
- else:
- return self.document._elementTree.getroot()
-
- def getFragment(self):
- fragment = []
- element = self.openElements[0]._element
- if element.text:
- fragment.append(element.text)
- fragment.extend(list(element))
- if element.tail:
- fragment.append(element.tail)
- return fragment
-
- def insertDoctype(self, token):
- name = token["name"]
- publicId = token["publicId"]
- systemId = token["systemId"]
-
- if not name:
- warnings.warn("lxml cannot represent empty doctype", DataLossWarning)
- self.doctype = None
- else:
- coercedName = self.infosetFilter.coerceElement(name)
- if coercedName != name:
- warnings.warn("lxml cannot represent non-xml doctype", DataLossWarning)
-
- doctype = self.doctypeClass(coercedName, publicId, systemId)
- self.doctype = doctype
-
- def insertCommentInitial(self, data, parent=None):
- assert parent is None or parent is self.document
- assert self.document._elementTree is None
- self.initial_comments.append(data)
-
- def insertCommentMain(self, data, parent=None):
- if (parent == self.document and
- self.document._elementTree.getroot()[-1].tag == comment_type):
- warnings.warn("lxml cannot represent adjacent comments beyond the root elements", DataLossWarning)
- super(TreeBuilder, self).insertComment(data, parent)
-
- def insertRoot(self, token):
- # Because of the way libxml2 works, it doesn't seem to be possible to
- # alter information like the doctype after the tree has been parsed.
- # Therefore we need to use the built-in parser to create our initial
- # tree, after which we can add elements like normal
- docStr = ""
- if self.doctype:
- assert self.doctype.name
- docStr += "<!DOCTYPE %s" % self.doctype.name
- if (self.doctype.publicId is not None or
- self.doctype.systemId is not None):
- docStr += (' PUBLIC "%s" ' %
- (self.infosetFilter.coercePubid(self.doctype.publicId or "")))
- if self.doctype.systemId:
- sysid = self.doctype.systemId
- if sysid.find("'") >= 0 and sysid.find('"') >= 0:
- warnings.warn("DOCTYPE system cannot contain single and double quotes", DataLossWarning)
- sysid = sysid.replace("'", 'U00027')
- if sysid.find("'") >= 0:
- docStr += '"%s"' % sysid
- else:
- docStr += "'%s'" % sysid
- else:
- docStr += "''"
- docStr += ">"
- if self.doctype.name != token["name"]:
- warnings.warn("lxml cannot represent doctype with a different name to the root element", DataLossWarning)
- docStr += "<THIS_SHOULD_NEVER_APPEAR_PUBLICLY/>"
- root = etree.fromstring(docStr)
-
- # Append the initial comments:
- for comment_token in self.initial_comments:
- comment = self.commentClass(comment_token["data"])
- root.addprevious(comment._element)
-
- # Create the root document and add the ElementTree to it
- self.document = self.documentClass()
- self.document._elementTree = root.getroottree()
-
- # Give the root element the right name
- name = token["name"]
- namespace = token.get("namespace", self.defaultNamespace)
- if namespace is None:
- etree_tag = name
- else:
- etree_tag = "{%s}%s" % (namespace, name)
- root.tag = etree_tag
-
- # Add the root element to the internal child/open data structures
- root_element = self.elementClass(name, namespace)
- root_element._element = root
- self.document._childNodes.append(root_element)
- self.openElements.append(root_element)
-
- # Reset to the default insert comment function
- self.insertComment = self.insertCommentMain
diff --git a/src/pip/_vendor/html5lib/treewalkers/__init__.py b/src/pip/_vendor/html5lib/treewalkers/__init__.py
deleted file mode 100644
index b2d3aac31..000000000
--- a/src/pip/_vendor/html5lib/treewalkers/__init__.py
+++ /dev/null
@@ -1,154 +0,0 @@
-"""A collection of modules for iterating through different kinds of
-tree, generating tokens identical to those produced by the tokenizer
-module.
-
-To create a tree walker for a new type of tree, you need to
-implement a tree walker object (called TreeWalker by convention) that
-implements a 'serialize' method which takes a tree as sole argument and
-returns an iterator which generates tokens.
-"""
-
-from __future__ import absolute_import, division, unicode_literals
-
-from .. import constants
-from .._utils import default_etree
-
-__all__ = ["getTreeWalker", "pprint"]
-
-treeWalkerCache = {}
-
-
-def getTreeWalker(treeType, implementation=None, **kwargs):
- """Get a TreeWalker class for various types of tree with built-in support
-
- :arg str treeType: the name of the tree type required (case-insensitive).
- Supported values are:
-
- * "dom": The xml.dom.minidom DOM implementation
- * "etree": A generic walker for tree implementations exposing an
- elementtree-like interface (known to work with ElementTree,
- cElementTree and lxml.etree).
- * "lxml": Optimized walker for lxml.etree
- * "genshi": a Genshi stream
-
- :arg implementation: A module implementing the tree type e.g.
- xml.etree.ElementTree or cElementTree (Currently applies to the "etree"
- tree type only).
-
- :arg kwargs: keyword arguments passed to the etree walker--for other
- walkers, this has no effect
-
- :returns: a TreeWalker class
-
- """
-
- treeType = treeType.lower()
- if treeType not in treeWalkerCache:
- if treeType == "dom":
- from . import dom
- treeWalkerCache[treeType] = dom.TreeWalker
- elif treeType == "genshi":
- from . import genshi
- treeWalkerCache[treeType] = genshi.TreeWalker
- elif treeType == "lxml":
- from . import etree_lxml
- treeWalkerCache[treeType] = etree_lxml.TreeWalker
- elif treeType == "etree":
- from . import etree
- if implementation is None:
- implementation = default_etree
- # XXX: NEVER cache here, caching is done in the etree submodule
- return etree.getETreeModule(implementation, **kwargs).TreeWalker
- return treeWalkerCache.get(treeType)
-
-
-def concatenateCharacterTokens(tokens):
- pendingCharacters = []
- for token in tokens:
- type = token["type"]
- if type in ("Characters", "SpaceCharacters"):
- pendingCharacters.append(token["data"])
- else:
- if pendingCharacters:
- yield {"type": "Characters", "data": "".join(pendingCharacters)}
- pendingCharacters = []
- yield token
- if pendingCharacters:
- yield {"type": "Characters", "data": "".join(pendingCharacters)}
-
-
-def pprint(walker):
- """Pretty printer for tree walkers
-
- Takes a TreeWalker instance and pretty prints the output of walking the tree.
-
- :arg walker: a TreeWalker instance
-
- """
- output = []
- indent = 0
- for token in concatenateCharacterTokens(walker):
- type = token["type"]
- if type in ("StartTag", "EmptyTag"):
- # tag name
- if token["namespace"] and token["namespace"] != constants.namespaces["html"]:
- if token["namespace"] in constants.prefixes:
- ns = constants.prefixes[token["namespace"]]
- else:
- ns = token["namespace"]
- name = "%s %s" % (ns, token["name"])
- else:
- name = token["name"]
- output.append("%s<%s>" % (" " * indent, name))
- indent += 2
- # attributes (sorted for consistent ordering)
- attrs = token["data"]
- for (namespace, localname), value in sorted(attrs.items()):
- if namespace:
- if namespace in constants.prefixes:
- ns = constants.prefixes[namespace]
- else:
- ns = namespace
- name = "%s %s" % (ns, localname)
- else:
- name = localname
- output.append("%s%s=\"%s\"" % (" " * indent, name, value))
- # self-closing
- if type == "EmptyTag":
- indent -= 2
-
- elif type == "EndTag":
- indent -= 2
-
- elif type == "Comment":
- output.append("%s<!-- %s -->" % (" " * indent, token["data"]))
-
- elif type == "Doctype":
- if token["name"]:
- if token["publicId"]:
- output.append("""%s<!DOCTYPE %s "%s" "%s">""" %
- (" " * indent,
- token["name"],
- token["publicId"],
- token["systemId"] if token["systemId"] else ""))
- elif token["systemId"]:
- output.append("""%s<!DOCTYPE %s "" "%s">""" %
- (" " * indent,
- token["name"],
- token["systemId"]))
- else:
- output.append("%s<!DOCTYPE %s>" % (" " * indent,
- token["name"]))
- else:
- output.append("%s<!DOCTYPE >" % (" " * indent,))
-
- elif type == "Characters":
- output.append("%s\"%s\"" % (" " * indent, token["data"]))
-
- elif type == "SpaceCharacters":
- assert False, "concatenateCharacterTokens should have got rid of all Space tokens"
-
- else:
- raise ValueError("Unknown token type, %s" % type)
-
- return "\n".join(output)
diff --git a/src/pip/_vendor/html5lib/treewalkers/base.py b/src/pip/_vendor/html5lib/treewalkers/base.py
deleted file mode 100644
index 80c474c4e..000000000
--- a/src/pip/_vendor/html5lib/treewalkers/base.py
+++ /dev/null
@@ -1,252 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-from xml.dom import Node
-from ..constants import namespaces, voidElements, spaceCharacters
-
-__all__ = ["DOCUMENT", "DOCTYPE", "TEXT", "ELEMENT", "COMMENT", "ENTITY", "UNKNOWN",
- "TreeWalker", "NonRecursiveTreeWalker"]
-
-DOCUMENT = Node.DOCUMENT_NODE
-DOCTYPE = Node.DOCUMENT_TYPE_NODE
-TEXT = Node.TEXT_NODE
-ELEMENT = Node.ELEMENT_NODE
-COMMENT = Node.COMMENT_NODE
-ENTITY = Node.ENTITY_NODE
-UNKNOWN = "<#UNKNOWN#>"
-
-spaceCharacters = "".join(spaceCharacters)
-
-
-class TreeWalker(object):
- """Walks a tree yielding tokens
-
- Tokens are dicts that all have a ``type`` field specifying the type of the
- token.
-
- """
- def __init__(self, tree):
- """Creates a TreeWalker
-
- :arg tree: the tree to walk
-
- """
- self.tree = tree
-
- def __iter__(self):
- raise NotImplementedError
-
- def error(self, msg):
- """Generates an error token with the given message
-
- :arg msg: the error message
-
- :returns: SerializeError token
-
- """
- return {"type": "SerializeError", "data": msg}
-
- def emptyTag(self, namespace, name, attrs, hasChildren=False):
- """Generates an EmptyTag token
-
- :arg namespace: the namespace of the token--can be ``None``
-
- :arg name: the name of the element
-
- :arg attrs: the attributes of the element as a dict
-
- :arg hasChildren: whether or not to yield a SerializationError because
- this tag shouldn't have children
-
- :returns: EmptyTag token
-
- """
- yield {"type": "EmptyTag", "name": name,
- "namespace": namespace,
- "data": attrs}
- if hasChildren:
- yield self.error("Void element has children")
-
- def startTag(self, namespace, name, attrs):
- """Generates a StartTag token
-
- :arg namespace: the namespace of the token--can be ``None``
-
- :arg name: the name of the element
-
- :arg attrs: the attributes of the element as a dict
-
- :returns: StartTag token
-
- """
- return {"type": "StartTag",
- "name": name,
- "namespace": namespace,
- "data": attrs}
-
- def endTag(self, namespace, name):
- """Generates an EndTag token
-
- :arg namespace: the namespace of the token--can be ``None``
-
- :arg name: the name of the element
-
- :returns: EndTag token
-
- """
- return {"type": "EndTag",
- "name": name,
- "namespace": namespace}
-
- def text(self, data):
- """Generates SpaceCharacters and Characters tokens
-
- Depending on what's in the data, this generates one or more
- ``SpaceCharacters`` and ``Characters`` tokens.
-
- For example:
-
- >>> from html5lib.treewalkers.base import TreeWalker
- >>> # Give it an empty tree just so it instantiates
- >>> walker = TreeWalker([])
- >>> list(walker.text(''))
- []
- >>> list(walker.text(' '))
- [{u'data': ' ', u'type': u'SpaceCharacters'}]
- >>> list(walker.text(' abc ')) # doctest: +NORMALIZE_WHITESPACE
- [{u'data': ' ', u'type': u'SpaceCharacters'},
- {u'data': u'abc', u'type': u'Characters'},
- {u'data': u' ', u'type': u'SpaceCharacters'}]
-
- :arg data: the text data
-
- :returns: one or more ``SpaceCharacters`` and ``Characters`` tokens
-
- """
- data = data
- middle = data.lstrip(spaceCharacters)
- left = data[:len(data) - len(middle)]
- if left:
- yield {"type": "SpaceCharacters", "data": left}
- data = middle
- middle = data.rstrip(spaceCharacters)
- right = data[len(middle):]
- if middle:
- yield {"type": "Characters", "data": middle}
- if right:
- yield {"type": "SpaceCharacters", "data": right}
-
- def comment(self, data):
- """Generates a Comment token
-
- :arg data: the comment
-
- :returns: Comment token
-
- """
- return {"type": "Comment", "data": data}
-
- def doctype(self, name, publicId=None, systemId=None):
- """Generates a Doctype token
-
- :arg name:
-
- :arg publicId:
-
- :arg systemId:
-
- :returns: the Doctype token
-
- """
- return {"type": "Doctype",
- "name": name,
- "publicId": publicId,
- "systemId": systemId}
-
- def entity(self, name):
- """Generates an Entity token
-
- :arg name: the entity name
-
- :returns: an Entity token
-
- """
- return {"type": "Entity", "name": name}
-
- def unknown(self, nodeType):
- """Handles unknown node types"""
- return self.error("Unknown node type: " + nodeType)
-
-
-class NonRecursiveTreeWalker(TreeWalker):
- def getNodeDetails(self, node):
- raise NotImplementedError
-
- def getFirstChild(self, node):
- raise NotImplementedError
-
- def getNextSibling(self, node):
- raise NotImplementedError
-
- def getParentNode(self, node):
- raise NotImplementedError
-
- def __iter__(self):
- currentNode = self.tree
- while currentNode is not None:
- details = self.getNodeDetails(currentNode)
- type, details = details[0], details[1:]
- hasChildren = False
-
- if type == DOCTYPE:
- yield self.doctype(*details)
-
- elif type == TEXT:
- for token in self.text(*details):
- yield token
-
- elif type == ELEMENT:
- namespace, name, attributes, hasChildren = details
- if (not namespace or namespace == namespaces["html"]) and name in voidElements:
- for token in self.emptyTag(namespace, name, attributes,
- hasChildren):
- yield token
- hasChildren = False
- else:
- yield self.startTag(namespace, name, attributes)
-
- elif type == COMMENT:
- yield self.comment(details[0])
-
- elif type == ENTITY:
- yield self.entity(details[0])
-
- elif type == DOCUMENT:
- hasChildren = True
-
- else:
- yield self.unknown(details[0])
-
- if hasChildren:
- firstChild = self.getFirstChild(currentNode)
- else:
- firstChild = None
-
- if firstChild is not None:
- currentNode = firstChild
- else:
- while currentNode is not None:
- details = self.getNodeDetails(currentNode)
- type, details = details[0], details[1:]
- if type == ELEMENT:
- namespace, name, attributes, hasChildren = details
- if (namespace and namespace != namespaces["html"]) or name not in voidElements:
- yield self.endTag(namespace, name)
- if self.tree is currentNode:
- currentNode = None
- break
- nextSibling = self.getNextSibling(currentNode)
- if nextSibling is not None:
- currentNode = nextSibling
- break
- else:
- currentNode = self.getParentNode(currentNode)
diff --git a/src/pip/_vendor/html5lib/treewalkers/dom.py b/src/pip/_vendor/html5lib/treewalkers/dom.py
deleted file mode 100644
index b0c89b001..000000000
--- a/src/pip/_vendor/html5lib/treewalkers/dom.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-from xml.dom import Node
-
-from . import base
-
-
-class TreeWalker(base.NonRecursiveTreeWalker):
- def getNodeDetails(self, node):
- if node.nodeType == Node.DOCUMENT_TYPE_NODE:
- return base.DOCTYPE, node.name, node.publicId, node.systemId
-
- elif node.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE):
- return base.TEXT, node.nodeValue
-
- elif node.nodeType == Node.ELEMENT_NODE:
- attrs = {}
- for attr in list(node.attributes.keys()):
- attr = node.getAttributeNode(attr)
- if attr.namespaceURI:
- attrs[(attr.namespaceURI, attr.localName)] = attr.value
- else:
- attrs[(None, attr.name)] = attr.value
- return (base.ELEMENT, node.namespaceURI, node.nodeName,
- attrs, node.hasChildNodes())
-
- elif node.nodeType == Node.COMMENT_NODE:
- return base.COMMENT, node.nodeValue
-
- elif node.nodeType in (Node.DOCUMENT_NODE, Node.DOCUMENT_FRAGMENT_NODE):
- return (base.DOCUMENT,)
-
- else:
- return base.UNKNOWN, node.nodeType
-
- def getFirstChild(self, node):
- return node.firstChild
-
- def getNextSibling(self, node):
- return node.nextSibling
-
- def getParentNode(self, node):
- return node.parentNode
diff --git a/src/pip/_vendor/html5lib/treewalkers/etree.py b/src/pip/_vendor/html5lib/treewalkers/etree.py
deleted file mode 100644
index 837b27ec4..000000000
--- a/src/pip/_vendor/html5lib/treewalkers/etree.py
+++ /dev/null
@@ -1,131 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-from collections import OrderedDict
-import re
-
-from pip._vendor.six import string_types
-
-from . import base
-from .._utils import moduleFactoryFactory
-
-tag_regexp = re.compile("{([^}]*)}(.*)")
-
-
-def getETreeBuilder(ElementTreeImplementation):
- ElementTree = ElementTreeImplementation
- ElementTreeCommentType = ElementTree.Comment("asd").tag
-
- class TreeWalker(base.NonRecursiveTreeWalker): # pylint:disable=unused-variable
- """Given the particular ElementTree representation, this implementation,
- to avoid using recursion, returns "nodes" as tuples with the following
- content:
-
- 1. The current element
-
- 2. The index of the element relative to its parent
-
- 3. A stack of ancestor elements
-
- 4. A flag "text", "tail" or None to indicate if the current node is a
- text node; either the text or tail of the current element (1)
- """
- def getNodeDetails(self, node):
- if isinstance(node, tuple): # It might be the root Element
- elt, _, _, flag = node
- if flag in ("text", "tail"):
- return base.TEXT, getattr(elt, flag)
- else:
- node = elt
-
- if not(hasattr(node, "tag")):
- node = node.getroot()
-
- if node.tag in ("DOCUMENT_ROOT", "DOCUMENT_FRAGMENT"):
- return (base.DOCUMENT,)
-
- elif node.tag == "<!DOCTYPE>":
- return (base.DOCTYPE, node.text,
- node.get("publicId"), node.get("systemId"))
-
- elif node.tag == ElementTreeCommentType:
- return base.COMMENT, node.text
-
- else:
- assert isinstance(node.tag, string_types), type(node.tag)
- # This is assumed to be an ordinary element
- match = tag_regexp.match(node.tag)
- if match:
- namespace, tag = match.groups()
- else:
- namespace = None
- tag = node.tag
- attrs = OrderedDict()
- for name, value in list(node.attrib.items()):
- match = tag_regexp.match(name)
- if match:
- attrs[(match.group(1), match.group(2))] = value
- else:
- attrs[(None, name)] = value
- return (base.ELEMENT, namespace, tag,
- attrs, len(node) or node.text)
-
- def getFirstChild(self, node):
- if isinstance(node, tuple):
- element, key, parents, flag = node
- else:
- element, key, parents, flag = node, None, [], None
-
- if flag in ("text", "tail"):
- return None
- else:
- if element.text:
- return element, key, parents, "text"
- elif len(element):
- parents.append(element)
- return element[0], 0, parents, None
- else:
- return None
-
- def getNextSibling(self, node):
- if isinstance(node, tuple):
- element, key, parents, flag = node
- else:
- return None
-
- if flag == "text":
- if len(element):
- parents.append(element)
- return element[0], 0, parents, None
- else:
- return None
- else:
- if element.tail and flag != "tail":
- return element, key, parents, "tail"
- elif key < len(parents[-1]) - 1:
- return parents[-1][key + 1], key + 1, parents, None
- else:
- return None
-
- def getParentNode(self, node):
- if isinstance(node, tuple):
- element, key, parents, flag = node
- else:
- return None
-
- if flag == "text":
- if not parents:
- return element
- else:
- return element, key, parents, None
- else:
- parent = parents.pop()
- if not parents:
- return parent
- else:
- assert list(parents[-1]).count(parent) == 1
- return parent, list(parents[-1]).index(parent), parents, None
-
- return locals()
-
-
-getETreeModule = moduleFactoryFactory(getETreeBuilder)
diff --git a/src/pip/_vendor/html5lib/treewalkers/etree_lxml.py b/src/pip/_vendor/html5lib/treewalkers/etree_lxml.py
deleted file mode 100644
index c56af390f..000000000
--- a/src/pip/_vendor/html5lib/treewalkers/etree_lxml.py
+++ /dev/null
@@ -1,215 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-from pip._vendor.six import text_type
-
-from collections import OrderedDict
-
-from lxml import etree
-from ..treebuilders.etree import tag_regexp
-
-from . import base
-
-from .. import _ihatexml
-
-
-def ensure_str(s):
- if s is None:
- return None
- elif isinstance(s, text_type):
- return s
- else:
- return s.decode("ascii", "strict")
-
-
-class Root(object):
- def __init__(self, et):
- self.elementtree = et
- self.children = []
-
- try:
- if et.docinfo.internalDTD:
- self.children.append(Doctype(self,
- ensure_str(et.docinfo.root_name),
- ensure_str(et.docinfo.public_id),
- ensure_str(et.docinfo.system_url)))
- except AttributeError:
- pass
-
- try:
- node = et.getroot()
- except AttributeError:
- node = et
-
- while node.getprevious() is not None:
- node = node.getprevious()
- while node is not None:
- self.children.append(node)
- node = node.getnext()
-
- self.text = None
- self.tail = None
-
- def __getitem__(self, key):
- return self.children[key]
-
- def getnext(self):
- return None
-
- def __len__(self):
- return 1
-
-
-class Doctype(object):
- def __init__(self, root_node, name, public_id, system_id):
- self.root_node = root_node
- self.name = name
- self.public_id = public_id
- self.system_id = system_id
-
- self.text = None
- self.tail = None
-
- def getnext(self):
- return self.root_node.children[1]
-
-
-class FragmentRoot(Root):
- def __init__(self, children):
- self.children = [FragmentWrapper(self, child) for child in children]
- self.text = self.tail = None
-
- def getnext(self):
- return None
-
-
-class FragmentWrapper(object):
- def __init__(self, fragment_root, obj):
- self.root_node = fragment_root
- self.obj = obj
- if hasattr(self.obj, 'text'):
- self.text = ensure_str(self.obj.text)
- else:
- self.text = None
- if hasattr(self.obj, 'tail'):
- self.tail = ensure_str(self.obj.tail)
- else:
- self.tail = None
-
- def __getattr__(self, name):
- return getattr(self.obj, name)
-
- def getnext(self):
- siblings = self.root_node.children
- idx = siblings.index(self)
- if idx < len(siblings) - 1:
- return siblings[idx + 1]
- else:
- return None
-
- def __getitem__(self, key):
- return self.obj[key]
-
- def __bool__(self):
- return bool(self.obj)
-
- def getparent(self):
- return None
-
- def __str__(self):
- return str(self.obj)
-
- def __unicode__(self):
- return str(self.obj)
-
- def __len__(self):
- return len(self.obj)
-
-
-class TreeWalker(base.NonRecursiveTreeWalker):
- def __init__(self, tree):
- # pylint:disable=redefined-variable-type
- if isinstance(tree, list):
- self.fragmentChildren = set(tree)
- tree = FragmentRoot(tree)
- else:
- self.fragmentChildren = set()
- tree = Root(tree)
- base.NonRecursiveTreeWalker.__init__(self, tree)
- self.filter = _ihatexml.InfosetFilter()
-
- def getNodeDetails(self, node):
- if isinstance(node, tuple): # Text node
- node, key = node
- assert key in ("text", "tail"), "Text nodes are text or tail, found %s" % key
- return base.TEXT, ensure_str(getattr(node, key))
-
- elif isinstance(node, Root):
- return (base.DOCUMENT,)
-
- elif isinstance(node, Doctype):
- return base.DOCTYPE, node.name, node.public_id, node.system_id
-
- elif isinstance(node, FragmentWrapper) and not hasattr(node, "tag"):
- return base.TEXT, ensure_str(node.obj)
-
- elif node.tag == etree.Comment:
- return base.COMMENT, ensure_str(node.text)
-
- elif node.tag == etree.Entity:
- return base.ENTITY, ensure_str(node.text)[1:-1] # strip &;
-
- else:
- # This is assumed to be an ordinary element
- match = tag_regexp.match(ensure_str(node.tag))
- if match:
- namespace, tag = match.groups()
- else:
- namespace = None
- tag = ensure_str(node.tag)
- attrs = OrderedDict()
- for name, value in list(node.attrib.items()):
- name = ensure_str(name)
- value = ensure_str(value)
- match = tag_regexp.match(name)
- if match:
- attrs[(match.group(1), match.group(2))] = value
- else:
- attrs[(None, name)] = value
- return (base.ELEMENT, namespace, self.filter.fromXmlName(tag),
- attrs, len(node) > 0 or node.text)
-
- def getFirstChild(self, node):
- assert not isinstance(node, tuple), "Text nodes have no children"
-
- assert len(node) or node.text, "Node has no children"
- if node.text:
- return (node, "text")
- else:
- return node[0]
-
- def getNextSibling(self, node):
- if isinstance(node, tuple): # Text node
- node, key = node
- assert key in ("text", "tail"), "Text nodes are text or tail, found %s" % key
- if key == "text":
- # XXX: we cannot use a "bool(node) and node[0] or None" construct here
- # because node[0] might evaluate to False if it has no child element
- if len(node):
- return node[0]
- else:
- return None
- else: # tail
- return node.getnext()
-
- return (node, "tail") if node.tail else node.getnext()
-
- def getParentNode(self, node):
- if isinstance(node, tuple): # Text node
- node, key = node
- assert key in ("text", "tail"), "Text nodes are text or tail, found %s" % key
- if key == "text":
- return node
- # else: fallback to "normal" processing
- elif node in self.fragmentChildren:
- return None
-
- return node.getparent()
diff --git a/src/pip/_vendor/html5lib/treewalkers/genshi.py b/src/pip/_vendor/html5lib/treewalkers/genshi.py
deleted file mode 100644
index 7483be27d..000000000
--- a/src/pip/_vendor/html5lib/treewalkers/genshi.py
+++ /dev/null
@@ -1,69 +0,0 @@
-from __future__ import absolute_import, division, unicode_literals
-
-from genshi.core import QName
-from genshi.core import START, END, XML_NAMESPACE, DOCTYPE, TEXT
-from genshi.core import START_NS, END_NS, START_CDATA, END_CDATA, PI, COMMENT
-
-from . import base
-
-from ..constants import voidElements, namespaces
-
-
-class TreeWalker(base.TreeWalker):
- def __iter__(self):
- # Buffer the events so we can pass in the following one
- previous = None
- for event in self.tree:
- if previous is not None:
- for token in self.tokens(previous, event):
- yield token
- previous = event
-
- # Don't forget the final event!
- if previous is not None:
- for token in self.tokens(previous, None):
- yield token
-
- def tokens(self, event, next):
- kind, data, _ = event
- if kind == START:
- tag, attribs = data
- name = tag.localname
- namespace = tag.namespace
- converted_attribs = {}
- for k, v in attribs:
- if isinstance(k, QName):
- converted_attribs[(k.namespace, k.localname)] = v
- else:
- converted_attribs[(None, k)] = v
-
- if namespace == namespaces["html"] and name in voidElements:
- for token in self.emptyTag(namespace, name, converted_attribs,
- not next or next[0] != END or
- next[1] != tag):
- yield token
- else:
- yield self.startTag(namespace, name, converted_attribs)
-
- elif kind == END:
- name = data.localname
- namespace = data.namespace
- if namespace != namespaces["html"] or name not in voidElements:
- yield self.endTag(namespace, name)
-
- elif kind == COMMENT:
- yield self.comment(data)
-
- elif kind == TEXT:
- for token in self.text(data):
- yield token
-
- elif kind == DOCTYPE:
- yield self.doctype(*data)
-
- elif kind in (XML_NAMESPACE, DOCTYPE, START_NS, END_NS,
- START_CDATA, END_CDATA, PI):
- pass
-
- else:
- yield self.unknown(kind)
diff --git a/src/pip/_vendor/idna/codec.py b/src/pip/_vendor/idna/codec.py
index 080f22a3b..1ca9ba62c 100644
--- a/src/pip/_vendor/idna/codec.py
+++ b/src/pip/_vendor/idna/codec.py
@@ -7,8 +7,7 @@ _unicode_dots_re = re.compile('[\u002e\u3002\uff0e\uff61]')
class Codec(codecs.Codec):
- def encode(self, data, errors='strict'):
- # type: (str, str) -> Tuple[bytes, int]
+ def encode(self, data: str, errors: str = 'strict') -> Tuple[bytes, int]:
if errors != 'strict':
raise IDNAError('Unsupported error handling \"{}\"'.format(errors))
@@ -17,8 +16,7 @@ class Codec(codecs.Codec):
return encode(data), len(data)
- def decode(self, data, errors='strict'):
- # type: (bytes, str) -> Tuple[str, int]
+ def decode(self, data: bytes, errors: str = 'strict') -> Tuple[str, int]:
if errors != 'strict':
raise IDNAError('Unsupported error handling \"{}\"'.format(errors))
@@ -28,8 +26,7 @@ class Codec(codecs.Codec):
return decode(data), len(data)
class IncrementalEncoder(codecs.BufferedIncrementalEncoder):
- def _buffer_encode(self, data, errors, final): # type: ignore
- # type: (str, str, bool) -> Tuple[str, int]
+ def _buffer_encode(self, data: str, errors: str, final: bool) -> Tuple[str, int]: # type: ignore
if errors != 'strict':
raise IDNAError('Unsupported error handling \"{}\"'.format(errors))
@@ -62,8 +59,7 @@ class IncrementalEncoder(codecs.BufferedIncrementalEncoder):
return result_str, size
class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
- def _buffer_decode(self, data, errors, final): # type: ignore
- # type: (str, str, bool) -> Tuple[str, int]
+ def _buffer_decode(self, data: str, errors: str, final: bool) -> Tuple[str, int]: # type: ignore
if errors != 'strict':
raise IDNAError('Unsupported error handling \"{}\"'.format(errors))
@@ -103,8 +99,7 @@ class StreamReader(Codec, codecs.StreamReader):
pass
-def getregentry():
- # type: () -> codecs.CodecInfo
+def getregentry() -> codecs.CodecInfo:
# Compatibility as a search_function for codecs.register()
return codecs.CodecInfo(
name='idna',
diff --git a/src/pip/_vendor/idna/compat.py b/src/pip/_vendor/idna/compat.py
index dc896c766..786e6bda6 100644
--- a/src/pip/_vendor/idna/compat.py
+++ b/src/pip/_vendor/idna/compat.py
@@ -2,15 +2,12 @@ from .core import *
from .codec import *
from typing import Any, Union
-def ToASCII(label):
- # type: (str) -> bytes
+def ToASCII(label: str) -> bytes:
return encode(label)
-def ToUnicode(label):
- # type: (Union[bytes, bytearray]) -> str
+def ToUnicode(label: Union[bytes, bytearray]) -> str:
return decode(label)
-def nameprep(s):
- # type: (Any) -> None
+def nameprep(s: Any) -> None:
raise NotImplementedError('IDNA 2008 does not utilise nameprep protocol')
diff --git a/src/pip/_vendor/idna/core.py b/src/pip/_vendor/idna/core.py
index d6051297d..4f3003711 100644
--- a/src/pip/_vendor/idna/core.py
+++ b/src/pip/_vendor/idna/core.py
@@ -29,43 +29,36 @@ class InvalidCodepointContext(IDNAError):
pass
-def _combining_class(cp):
- # type: (int) -> int
+def _combining_class(cp: int) -> int:
v = unicodedata.combining(chr(cp))
if v == 0:
if not unicodedata.name(chr(cp)):
raise ValueError('Unknown character in unicodedata')
return v
-def _is_script(cp, script):
- # type: (str, str) -> bool
+def _is_script(cp: str, script: str) -> bool:
return intranges_contain(ord(cp), idnadata.scripts[script])
-def _punycode(s):
- # type: (str) -> bytes
+def _punycode(s: str) -> bytes:
return s.encode('punycode')
-def _unot(s):
- # type: (int) -> str
+def _unot(s: int) -> str:
return 'U+{:04X}'.format(s)
-def valid_label_length(label):
- # type: (Union[bytes, str]) -> bool
+def valid_label_length(label: Union[bytes, str]) -> bool:
if len(label) > 63:
return False
return True
-def valid_string_length(label, trailing_dot):
- # type: (Union[bytes, str], bool) -> bool
+def valid_string_length(label: Union[bytes, str], trailing_dot: bool) -> bool:
if len(label) > (254 if trailing_dot else 253):
return False
return True
-def check_bidi(label, check_ltr=False):
- # type: (str, bool) -> bool
+def check_bidi(label: str, check_ltr: bool = False) -> bool:
# Bidi rules should only be applied if string contains RTL characters
bidi_label = False
for (idx, cp) in enumerate(label, 1):
@@ -124,15 +117,13 @@ def check_bidi(label, check_ltr=False):
return True
-def check_initial_combiner(label):
- # type: (str) -> bool
+def check_initial_combiner(label: str) -> bool:
if unicodedata.category(label[0])[0] == 'M':
raise IDNAError('Label begins with an illegal combining character')
return True
-def check_hyphen_ok(label):
- # type: (str) -> bool
+def check_hyphen_ok(label: str) -> bool:
if label[2:4] == '--':
raise IDNAError('Label has disallowed hyphens in 3rd and 4th position')
if label[0] == '-' or label[-1] == '-':
@@ -140,14 +131,12 @@ def check_hyphen_ok(label):
return True
-def check_nfc(label):
- # type: (str) -> None
+def check_nfc(label: str) -> None:
if unicodedata.normalize('NFC', label) != label:
raise IDNAError('Label must be in Normalization Form C')
-def valid_contextj(label, pos):
- # type: (str, int) -> bool
+def valid_contextj(label: str, pos: int) -> bool:
cp_value = ord(label[pos])
if cp_value == 0x200c:
@@ -190,8 +179,7 @@ def valid_contextj(label, pos):
return False
-def valid_contexto(label, pos, exception=False):
- # type: (str, int, bool) -> bool
+def valid_contexto(label: str, pos: int, exception: bool = False) -> bool:
cp_value = ord(label[pos])
if cp_value == 0x00b7:
@@ -233,8 +221,7 @@ def valid_contexto(label, pos, exception=False):
return False
-def check_label(label):
- # type: (Union[str, bytes, bytearray]) -> None
+def check_label(label: Union[str, bytes, bytearray]) -> None:
if isinstance(label, (bytes, bytearray)):
label = label.decode('utf-8')
if len(label) == 0:
@@ -265,8 +252,7 @@ def check_label(label):
check_bidi(label)
-def alabel(label):
- # type: (str) -> bytes
+def alabel(label: str) -> bytes:
try:
label_bytes = label.encode('ascii')
ulabel(label_bytes)
@@ -290,8 +276,7 @@ def alabel(label):
return label_bytes
-def ulabel(label):
- # type: (Union[str, bytes, bytearray]) -> str
+def ulabel(label: Union[str, bytes, bytearray]) -> str:
if not isinstance(label, (bytes, bytearray)):
try:
label_bytes = label.encode('ascii')
@@ -312,13 +297,15 @@ def ulabel(label):
check_label(label_bytes)
return label_bytes.decode('ascii')
- label = label_bytes.decode('punycode')
+ try:
+ label = label_bytes.decode('punycode')
+ except UnicodeError:
+ raise IDNAError('Invalid A-label')
check_label(label)
return label
-def uts46_remap(domain, std3_rules=True, transitional=False):
- # type: (str, bool, bool) -> str
+def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str:
"""Re-map the characters in the string according to UTS46 processing."""
from .uts46data import uts46data
output = ''
@@ -350,10 +337,12 @@ def uts46_remap(domain, std3_rules=True, transitional=False):
return unicodedata.normalize('NFC', output)
-def encode(s, strict=False, uts46=False, std3_rules=False, transitional=False):
- # type: (Union[str, bytes, bytearray], bool, bool, bool, bool) -> bytes
+def encode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = False, std3_rules: bool = False, transitional: bool = False) -> bytes:
if isinstance(s, (bytes, bytearray)):
- s = s.decode('ascii')
+ try:
+ s = s.decode('ascii')
+ except UnicodeDecodeError:
+ raise IDNAError('should pass a unicode string to the function rather than a byte string.')
if uts46:
s = uts46_remap(s, std3_rules, transitional)
trailing_dot = False
@@ -381,10 +370,12 @@ def encode(s, strict=False, uts46=False, std3_rules=False, transitional=False):
return s
-def decode(s, strict=False, uts46=False, std3_rules=False):
- # type: (Union[str, bytes, bytearray], bool, bool, bool) -> str
- if isinstance(s, (bytes, bytearray)):
- s = s.decode('ascii')
+def decode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = False, std3_rules: bool = False) -> str:
+ try:
+ if isinstance(s, (bytes, bytearray)):
+ s = s.decode('ascii')
+ except UnicodeDecodeError:
+ raise IDNAError('Invalid ASCII in A-label')
if uts46:
s = uts46_remap(s, std3_rules, False)
trailing_dot = False
diff --git a/src/pip/_vendor/idna/idnadata.py b/src/pip/_vendor/idna/idnadata.py
index b86a3e06e..67db46258 100644
--- a/src/pip/_vendor/idna/idnadata.py
+++ b/src/pip/_vendor/idna/idnadata.py
@@ -1,6 +1,6 @@
# This file is automatically generated by tools/idna-data
-__version__ = '13.0.0'
+__version__ = '15.0.0'
scripts = {
'Greek': (
0x37000000374,
@@ -49,17 +49,19 @@ scripts = {
0x30210000302a,
0x30380000303c,
0x340000004dc0,
- 0x4e0000009ffd,
+ 0x4e000000a000,
0xf9000000fa6e,
0xfa700000fada,
+ 0x16fe200016fe4,
0x16ff000016ff2,
- 0x200000002a6de,
- 0x2a7000002b735,
+ 0x200000002a6e0,
+ 0x2a7000002b73a,
0x2b7400002b81e,
0x2b8200002cea2,
0x2ceb00002ebe1,
0x2f8000002fa1e,
0x300000003134b,
+ 0x31350000323b0,
),
'Hebrew': (
0x591000005c8,
@@ -75,7 +77,8 @@ scripts = {
'Hiragana': (
0x304100003097,
0x309d000030a0,
- 0x1b0010001b11f,
+ 0x1b0010001b120,
+ 0x1b1320001b133,
0x1b1500001b153,
0x1f2000001f201,
),
@@ -87,7 +90,12 @@ scripts = {
0x330000003358,
0xff660000ff70,
0xff710000ff9e,
+ 0x1aff00001aff4,
+ 0x1aff50001affc,
+ 0x1affd0001afff,
0x1b0000001b001,
+ 0x1b1200001b123,
+ 0x1b1550001b156,
0x1b1640001b168,
),
}
@@ -405,6 +413,39 @@ joining_types = {
0x868: 68,
0x869: 82,
0x86a: 82,
+ 0x870: 82,
+ 0x871: 82,
+ 0x872: 82,
+ 0x873: 82,
+ 0x874: 82,
+ 0x875: 82,
+ 0x876: 82,
+ 0x877: 82,
+ 0x878: 82,
+ 0x879: 82,
+ 0x87a: 82,
+ 0x87b: 82,
+ 0x87c: 82,
+ 0x87d: 82,
+ 0x87e: 82,
+ 0x87f: 82,
+ 0x880: 82,
+ 0x881: 82,
+ 0x882: 82,
+ 0x883: 67,
+ 0x884: 67,
+ 0x885: 67,
+ 0x886: 68,
+ 0x887: 85,
+ 0x888: 85,
+ 0x889: 68,
+ 0x88a: 68,
+ 0x88b: 68,
+ 0x88c: 68,
+ 0x88d: 68,
+ 0x88e: 82,
+ 0x890: 85,
+ 0x891: 85,
0x8a0: 68,
0x8a1: 68,
0x8a2: 68,
@@ -426,6 +467,7 @@ joining_types = {
0x8b2: 82,
0x8b3: 68,
0x8b4: 68,
+ 0x8b5: 68,
0x8b6: 68,
0x8b7: 68,
0x8b8: 68,
@@ -444,6 +486,7 @@ joining_types = {
0x8c5: 68,
0x8c6: 68,
0x8c7: 68,
+ 0x8c8: 68,
0x8e2: 85,
0x1806: 85,
0x1807: 68,
@@ -768,6 +811,24 @@ joining_types = {
0x10f52: 68,
0x10f53: 68,
0x10f54: 82,
+ 0x10f70: 68,
+ 0x10f71: 68,
+ 0x10f72: 68,
+ 0x10f73: 68,
+ 0x10f74: 82,
+ 0x10f75: 82,
+ 0x10f76: 68,
+ 0x10f77: 68,
+ 0x10f78: 68,
+ 0x10f79: 68,
+ 0x10f7a: 68,
+ 0x10f7b: 68,
+ 0x10f7c: 68,
+ 0x10f7d: 68,
+ 0x10f7e: 68,
+ 0x10f7f: 68,
+ 0x10f80: 68,
+ 0x10f81: 68,
0x10fb0: 68,
0x10fb1: 85,
0x10fb2: 68,
@@ -1168,9 +1229,9 @@ codepoint_classes = {
0x8000000082e,
0x8400000085c,
0x8600000086b,
- 0x8a0000008b5,
- 0x8b6000008c8,
- 0x8d3000008e2,
+ 0x87000000888,
+ 0x8890000088f,
+ 0x898000008e2,
0x8e300000958,
0x96000000964,
0x96600000970,
@@ -1252,11 +1313,12 @@ codepoint_classes = {
0xc0e00000c11,
0xc1200000c29,
0xc2a00000c3a,
- 0xc3d00000c45,
+ 0xc3c00000c45,
0xc4600000c49,
0xc4a00000c4e,
0xc5500000c57,
0xc5800000c5b,
+ 0xc5d00000c5e,
0xc6000000c64,
0xc6600000c70,
0xc8000000c84,
@@ -1269,10 +1331,10 @@ codepoint_classes = {
0xcc600000cc9,
0xcca00000cce,
0xcd500000cd7,
- 0xcde00000cdf,
+ 0xcdd00000cdf,
0xce000000ce4,
0xce600000cf0,
- 0xcf100000cf3,
+ 0xcf100000cf4,
0xd0000000d0d,
0xd0e00000d11,
0xd1200000d45,
@@ -1307,7 +1369,7 @@ codepoint_classes = {
0xeb400000ebe,
0xec000000ec5,
0xec600000ec7,
- 0xec800000ece,
+ 0xec800000ecf,
0xed000000eda,
0xede00000ee0,
0xf0000000f01,
@@ -1366,9 +1428,8 @@ codepoint_classes = {
0x16810000169b,
0x16a0000016eb,
0x16f1000016f9,
- 0x17000000170d,
- 0x170e00001715,
- 0x172000001735,
+ 0x170000001716,
+ 0x171f00001735,
0x174000001754,
0x17600000176d,
0x176e00001771,
@@ -1397,8 +1458,8 @@ codepoint_classes = {
0x1a9000001a9a,
0x1aa700001aa8,
0x1ab000001abe,
- 0x1abf00001ac1,
- 0x1b0000001b4c,
+ 0x1abf00001acf,
+ 0x1b0000001b4d,
0x1b5000001b5a,
0x1b6b00001b74,
0x1b8000001bf4,
@@ -1413,8 +1474,7 @@ codepoint_classes = {
0x1d4e00001d4f,
0x1d6b00001d78,
0x1d7900001d9b,
- 0x1dc000001dfa,
- 0x1dfb00001e00,
+ 0x1dc000001e00,
0x1e0100001e02,
0x1e0300001e04,
0x1e0500001e06,
@@ -1563,7 +1623,7 @@ codepoint_classes = {
0x1ff600001ff7,
0x214e0000214f,
0x218400002185,
- 0x2c3000002c5f,
+ 0x2c3000002c60,
0x2c6100002c62,
0x2c6500002c67,
0x2c6800002c69,
@@ -1652,8 +1712,7 @@ codepoint_classes = {
0x31a0000031c0,
0x31f000003200,
0x340000004dc0,
- 0x4e0000009ffd,
- 0xa0000000a48d,
+ 0x4e000000a48d,
0xa4d00000a4fe,
0xa5000000a60d,
0xa6100000a62c,
@@ -1766,9 +1825,16 @@ codepoint_classes = {
0xa7bb0000a7bc,
0xa7bd0000a7be,
0xa7bf0000a7c0,
+ 0xa7c10000a7c2,
0xa7c30000a7c4,
0xa7c80000a7c9,
0xa7ca0000a7cb,
+ 0xa7d10000a7d2,
+ 0xa7d30000a7d4,
+ 0xa7d50000a7d6,
+ 0xa7d70000a7d8,
+ 0xa7d90000a7da,
+ 0xa7f20000a7f5,
0xa7f60000a7f8,
0xa7fa0000a828,
0xa82c0000a82d,
@@ -1796,7 +1862,7 @@ codepoint_classes = {
0xab200000ab27,
0xab280000ab2f,
0xab300000ab5b,
- 0xab600000ab6a,
+ 0xab600000ab69,
0xabc00000abeb,
0xabec0000abee,
0xabf00000abfa,
@@ -1834,9 +1900,16 @@ codepoint_classes = {
0x104d8000104fc,
0x1050000010528,
0x1053000010564,
+ 0x10597000105a2,
+ 0x105a3000105b2,
+ 0x105b3000105ba,
+ 0x105bb000105bd,
0x1060000010737,
0x1074000010756,
0x1076000010768,
+ 0x1078000010786,
+ 0x10787000107b1,
+ 0x107b2000107bb,
0x1080000010806,
0x1080800010809,
0x1080a00010836,
@@ -1873,14 +1946,16 @@ codepoint_classes = {
0x10e8000010eaa,
0x10eab00010ead,
0x10eb000010eb2,
- 0x10f0000010f1d,
+ 0x10efd00010f1d,
0x10f2700010f28,
0x10f3000010f51,
+ 0x10f7000010f86,
0x10fb000010fc5,
0x10fe000010ff7,
0x1100000011047,
- 0x1106600011070,
+ 0x1106600011076,
0x1107f000110bb,
+ 0x110c2000110c3,
0x110d0000110e9,
0x110f0000110fa,
0x1110000011135,
@@ -1894,7 +1969,7 @@ codepoint_classes = {
0x111dc000111dd,
0x1120000011212,
0x1121300011238,
- 0x1123e0001123f,
+ 0x1123e00011242,
0x1128000011287,
0x1128800011289,
0x1128a0001128e,
@@ -1934,6 +2009,7 @@ codepoint_classes = {
0x117000001171b,
0x1171d0001172c,
0x117300001173a,
+ 0x1174000011747,
0x118000001183b,
0x118c0000118ea,
0x118ff00011907,
@@ -1952,7 +2028,7 @@ codepoint_classes = {
0x11a4700011a48,
0x11a5000011a9a,
0x11a9d00011a9e,
- 0x11ac000011af9,
+ 0x11ab000011af9,
0x11c0000011c09,
0x11c0a00011c37,
0x11c3800011c41,
@@ -1974,14 +2050,22 @@ codepoint_classes = {
0x11d9300011d99,
0x11da000011daa,
0x11ee000011ef7,
+ 0x11f0000011f11,
+ 0x11f1200011f3b,
+ 0x11f3e00011f43,
+ 0x11f5000011f5a,
0x11fb000011fb1,
0x120000001239a,
0x1248000012544,
- 0x130000001342f,
+ 0x12f9000012ff1,
+ 0x1300000013430,
+ 0x1344000013456,
0x1440000014647,
0x1680000016a39,
0x16a4000016a5f,
0x16a6000016a6a,
+ 0x16a7000016abf,
+ 0x16ac000016aca,
0x16ad000016aee,
0x16af000016af5,
0x16b0000016b37,
@@ -1999,8 +2083,13 @@ codepoint_classes = {
0x17000000187f8,
0x1880000018cd6,
0x18d0000018d09,
- 0x1b0000001b11f,
+ 0x1aff00001aff4,
+ 0x1aff50001affc,
+ 0x1affd0001afff,
+ 0x1b0000001b123,
+ 0x1b1320001b133,
0x1b1500001b153,
+ 0x1b1550001b156,
0x1b1640001b168,
0x1b1700001b2fc,
0x1bc000001bc6b,
@@ -2008,33 +2097,45 @@ codepoint_classes = {
0x1bc800001bc89,
0x1bc900001bc9a,
0x1bc9d0001bc9f,
+ 0x1cf000001cf2e,
+ 0x1cf300001cf47,
0x1da000001da37,
0x1da3b0001da6d,
0x1da750001da76,
0x1da840001da85,
0x1da9b0001daa0,
0x1daa10001dab0,
+ 0x1df000001df1f,
+ 0x1df250001df2b,
0x1e0000001e007,
0x1e0080001e019,
0x1e01b0001e022,
0x1e0230001e025,
0x1e0260001e02b,
+ 0x1e0300001e06e,
+ 0x1e08f0001e090,
0x1e1000001e12d,
0x1e1300001e13e,
0x1e1400001e14a,
0x1e14e0001e14f,
+ 0x1e2900001e2af,
0x1e2c00001e2fa,
+ 0x1e4d00001e4fa,
+ 0x1e7e00001e7e7,
+ 0x1e7e80001e7ec,
+ 0x1e7ed0001e7ef,
+ 0x1e7f00001e7ff,
0x1e8000001e8c5,
0x1e8d00001e8d7,
0x1e9220001e94c,
0x1e9500001e95a,
- 0x1fbf00001fbfa,
- 0x200000002a6de,
- 0x2a7000002b735,
+ 0x200000002a6e0,
+ 0x2a7000002b73a,
0x2b7400002b81e,
0x2b8200002cea2,
0x2ceb00002ebe1,
0x300000003134b,
+ 0x31350000323b0,
),
'CONTEXTJ': (
0x200c0000200e,
diff --git a/src/pip/_vendor/idna/intranges.py b/src/pip/_vendor/idna/intranges.py
index ee364a904..6a43b0475 100644
--- a/src/pip/_vendor/idna/intranges.py
+++ b/src/pip/_vendor/idna/intranges.py
@@ -8,8 +8,7 @@ in the original list?" in time O(log(# runs)).
import bisect
from typing import List, Tuple
-def intranges_from_list(list_):
- # type: (List[int]) -> Tuple[int, ...]
+def intranges_from_list(list_: List[int]) -> Tuple[int, ...]:
"""Represent a list of integers as a sequence of ranges:
((start_0, end_0), (start_1, end_1), ...), such that the original
integers are exactly those x such that start_i <= x < end_i for some i.
@@ -30,17 +29,14 @@ def intranges_from_list(list_):
return tuple(ranges)
-def _encode_range(start, end):
- # type: (int, int) -> int
+def _encode_range(start: int, end: int) -> int:
return (start << 32) | end
-def _decode_range(r):
- # type: (int) -> Tuple[int, int]
+def _decode_range(r: int) -> Tuple[int, int]:
return (r >> 32), (r & ((1 << 32) - 1))
-def intranges_contain(int_, ranges):
- # type: (int, Tuple[int, ...]) -> bool
+def intranges_contain(int_: int, ranges: Tuple[int, ...]) -> bool:
"""Determine if `int_` falls into one of the ranges in `ranges`."""
tuple_ = _encode_range(int_, 0)
pos = bisect.bisect_left(ranges, tuple_)
diff --git a/src/pip/_vendor/idna/package_data.py b/src/pip/_vendor/idna/package_data.py
index e096d1d52..8501893bd 100644
--- a/src/pip/_vendor/idna/package_data.py
+++ b/src/pip/_vendor/idna/package_data.py
@@ -1,2 +1,2 @@
-__version__ = '3.2'
+__version__ = '3.4'
diff --git a/src/pip/_vendor/idna/uts46data.py b/src/pip/_vendor/idna/uts46data.py
index f382ce389..186796c17 100644
--- a/src/pip/_vendor/idna/uts46data.py
+++ b/src/pip/_vendor/idna/uts46data.py
@@ -1,13 +1,14 @@
# This file is automatically generated by tools/idna-data
+# vim: set fileencoding=utf-8 :
from typing import List, Tuple, Union
+
"""IDNA Mapping Table from UTS46."""
-__version__ = '13.0.0'
-def _seg_0():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+__version__ = '15.0.0'
+def _seg_0() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
(0x0, '3'),
(0x1, '3'),
@@ -111,8 +112,7 @@ def _seg_0():
(0x63, 'V'),
]
-def _seg_1():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_1() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
(0x64, 'V'),
(0x65, 'V'),
@@ -216,8 +216,7 @@ def _seg_1():
(0xC7, 'M', 'ç'),
]
-def _seg_2():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_2() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
(0xC8, 'M', 'è'),
(0xC9, 'M', 'é'),
@@ -321,8 +320,7 @@ def _seg_2():
(0x12B, 'V'),
]
-def _seg_3():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_3() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
(0x12C, 'M', 'ĭ'),
(0x12D, 'V'),
@@ -426,8 +424,7 @@ def _seg_3():
(0x193, 'M', 'ɠ'),
]
-def _seg_4():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_4() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
(0x194, 'M', 'ɣ'),
(0x195, 'V'),
@@ -531,8 +528,7 @@ def _seg_4():
(0x20C, 'M', 'ȍ'),
]
-def _seg_5():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_5() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
(0x20D, 'V'),
(0x20E, 'M', 'ȏ'),
@@ -636,8 +632,7 @@ def _seg_5():
(0x377, 'V'),
]
-def _seg_6():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_6() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
(0x378, 'X'),
(0x37A, '3', ' ι'),
@@ -741,8 +736,7 @@ def _seg_6():
(0x402, 'M', 'ђ'),
]
-def _seg_7():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_7() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
(0x403, 'M', 'ѓ'),
(0x404, 'M', 'є'),
@@ -846,8 +840,7 @@ def _seg_7():
(0x49D, 'V'),
]
-def _seg_8():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_8() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
(0x49E, 'M', 'ҟ'),
(0x49F, 'V'),
@@ -951,8 +944,7 @@ def _seg_8():
(0x502, 'M', 'ԃ'),
]
-def _seg_9():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_9() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
(0x503, 'V'),
(0x504, 'M', 'ԅ'),
@@ -1053,11 +1045,10 @@ def _seg_9():
(0x5F5, 'X'),
(0x606, 'V'),
(0x61C, 'X'),
- (0x61E, 'V'),
+ (0x61D, 'V'),
]
-def _seg_10():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_10() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
(0x675, 'M', 'اٴ'),
(0x676, 'M', 'وٴ'),
@@ -1083,11 +1074,9 @@ def _seg_10():
(0x85F, 'X'),
(0x860, 'V'),
(0x86B, 'X'),
- (0x8A0, 'V'),
- (0x8B5, 'X'),
- (0x8B6, 'V'),
- (0x8C8, 'X'),
- (0x8D3, 'V'),
+ (0x870, 'V'),
+ (0x88F, 'X'),
+ (0x898, 'V'),
(0x8E2, 'X'),
(0x8E3, 'V'),
(0x958, 'M', 'क़'),
@@ -1159,13 +1148,12 @@ def _seg_10():
(0xA59, 'M', 'ਖ਼'),
(0xA5A, 'M', 'ਗ਼'),
(0xA5B, 'M', 'ਜ਼'),
+ (0xA5C, 'V'),
+ (0xA5D, 'X'),
]
-def _seg_11():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_11() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
- (0xA5C, 'V'),
- (0xA5D, 'X'),
(0xA5E, 'M', 'ਫ਼'),
(0xA5F, 'X'),
(0xA66, 'V'),
@@ -1264,15 +1252,14 @@ def _seg_11():
(0xC0E, 'V'),
(0xC11, 'X'),
(0xC12, 'V'),
+ (0xC29, 'X'),
+ (0xC2A, 'V'),
]
-def _seg_12():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_12() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
- (0xC29, 'X'),
- (0xC2A, 'V'),
(0xC3A, 'X'),
- (0xC3D, 'V'),
+ (0xC3C, 'V'),
(0xC45, 'X'),
(0xC46, 'V'),
(0xC49, 'X'),
@@ -1282,6 +1269,8 @@ def _seg_12():
(0xC57, 'X'),
(0xC58, 'V'),
(0xC5B, 'X'),
+ (0xC5D, 'V'),
+ (0xC5E, 'X'),
(0xC60, 'V'),
(0xC64, 'X'),
(0xC66, 'V'),
@@ -1304,14 +1293,14 @@ def _seg_12():
(0xCCE, 'X'),
(0xCD5, 'V'),
(0xCD7, 'X'),
- (0xCDE, 'V'),
+ (0xCDD, 'V'),
(0xCDF, 'X'),
(0xCE0, 'V'),
(0xCE4, 'X'),
(0xCE6, 'V'),
(0xCF0, 'X'),
(0xCF1, 'V'),
- (0xCF3, 'X'),
+ (0xCF4, 'X'),
(0xD00, 'V'),
(0xD0D, 'X'),
(0xD0E, 'V'),
@@ -1371,8 +1360,7 @@ def _seg_12():
(0xEB4, 'V'),
]
-def _seg_13():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_13() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
(0xEBE, 'X'),
(0xEC0, 'V'),
@@ -1380,7 +1368,7 @@ def _seg_13():
(0xEC6, 'V'),
(0xEC7, 'X'),
(0xEC8, 'V'),
- (0xECE, 'X'),
+ (0xECF, 'X'),
(0xED0, 'V'),
(0xEDA, 'X'),
(0xEDC, 'M', 'ຫນ'),
@@ -1476,8 +1464,7 @@ def _seg_13():
(0x1312, 'V'),
]
-def _seg_14():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_14() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
(0x1316, 'X'),
(0x1318, 'V'),
@@ -1502,10 +1489,8 @@ def _seg_14():
(0x16A0, 'V'),
(0x16F9, 'X'),
(0x1700, 'V'),
- (0x170D, 'X'),
- (0x170E, 'V'),
- (0x1715, 'X'),
- (0x1720, 'V'),
+ (0x1716, 'X'),
+ (0x171F, 'V'),
(0x1737, 'X'),
(0x1740, 'V'),
(0x1754, 'X'),
@@ -1528,6 +1513,7 @@ def _seg_14():
(0x1807, 'V'),
(0x180B, 'I'),
(0x180E, 'X'),
+ (0x180F, 'I'),
(0x1810, 'V'),
(0x181A, 'X'),
(0x1820, 'V'),
@@ -1567,11 +1553,11 @@ def _seg_14():
(0x1AA0, 'V'),
(0x1AAE, 'X'),
(0x1AB0, 'V'),
- (0x1AC1, 'X'),
+ (0x1ACF, 'X'),
(0x1B00, 'V'),
- (0x1B4C, 'X'),
+ (0x1B4D, 'X'),
(0x1B50, 'V'),
- (0x1B7D, 'X'),
+ (0x1B7F, 'X'),
(0x1B80, 'V'),
(0x1BF4, 'X'),
(0x1BFC, 'V'),
@@ -1579,12 +1565,11 @@ def _seg_14():
(0x1C3B, 'V'),
(0x1C4A, 'X'),
(0x1C4D, 'V'),
+ (0x1C80, 'M', 'в'),
]
-def _seg_15():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_15() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
- (0x1C80, 'M', 'в'),
(0x1C81, 'M', 'д'),
(0x1C82, 'M', 'о'),
(0x1C83, 'M', 'с'),
@@ -1684,12 +1669,11 @@ def _seg_15():
(0x1D50, 'M', 'm'),
(0x1D51, 'M', 'ŋ'),
(0x1D52, 'M', 'o'),
+ (0x1D53, 'M', 'ɔ'),
]
-def _seg_16():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
+def _seg_16() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
return [
- (0x1D53, 'M', 'ɔ'),
(0x1D54, 'M', 'ᴖ'),
(0x1D55, 'M', 'ᴗ'),
(0x1D56, 'M', 'p'),
@@ -1754,8 +1738,6 @@ def _seg_16():
(0x1DBE, 'M', 'ʒ'),
(0x1DBF, 'M', 'θ'),
(0x1DC0, 'V'),
- (0x1DFA, 'X'),
- (0x1DFB, 'V'),
(0x1E00, 'M', 'ḁ'),
(0x1E01, 'V'),
(0x1E02, 'M', 'ḃ'),
@@ -1789,14 +1771,13 @@ def _seg_16():
(0x1E1E, 'M', 'ḟ'),
(0x1E1F, 'V'),
(0x1E20, 'M', 'ḡ'),
- ]
-
-def _seg_17():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1E21, 'V'),
(0x1E22, 'M', 'ḣ'),
(0x1E23, 'V'),
+ ]
+
+def _seg_17() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1E24, 'M', 'ḥ'),
(0x1E25, 'V'),
(0x1E26, 'M', 'ḧ'),
@@ -1894,14 +1875,13 @@ def _seg_17():
(0x1E82, 'M', 'ẃ'),
(0x1E83, 'V'),
(0x1E84, 'M', 'ẅ'),
- ]
-
-def _seg_18():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1E85, 'V'),
(0x1E86, 'M', 'ẇ'),
(0x1E87, 'V'),
+ ]
+
+def _seg_18() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1E88, 'M', 'ẉ'),
(0x1E89, 'V'),
(0x1E8A, 'M', 'ẋ'),
@@ -1999,14 +1979,13 @@ def _seg_18():
(0x1EEB, 'V'),
(0x1EEC, 'M', 'ử'),
(0x1EED, 'V'),
- ]
-
-def _seg_19():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1EEE, 'M', 'ữ'),
(0x1EEF, 'V'),
(0x1EF0, 'M', 'ự'),
+ ]
+
+def _seg_19() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1EF1, 'V'),
(0x1EF2, 'M', 'ỳ'),
(0x1EF3, 'V'),
@@ -2104,14 +2083,13 @@ def _seg_19():
(0x1F82, 'M', 'ἂι'),
(0x1F83, 'M', 'ἃι'),
(0x1F84, 'M', 'ἄι'),
- ]
-
-def _seg_20():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1F85, 'M', 'ἅι'),
(0x1F86, 'M', 'ἆι'),
(0x1F87, 'M', 'ἇι'),
+ ]
+
+def _seg_20() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1F88, 'M', 'ἀι'),
(0x1F89, 'M', 'ἁι'),
(0x1F8A, 'M', 'ἂι'),
@@ -2209,14 +2187,13 @@ def _seg_20():
(0x1FF0, 'X'),
(0x1FF2, 'M', 'ὼι'),
(0x1FF3, 'M', 'ωι'),
- ]
-
-def _seg_21():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1FF4, 'M', 'ώι'),
(0x1FF5, 'X'),
(0x1FF6, 'V'),
+ ]
+
+def _seg_21() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1FF7, 'M', 'ῶι'),
(0x1FF8, 'M', 'ὸ'),
(0x1FF9, 'M', 'ό'),
@@ -2309,19 +2286,18 @@ def _seg_21():
(0x20A0, 'V'),
(0x20A8, 'M', 'rs'),
(0x20A9, 'V'),
- (0x20C0, 'X'),
+ (0x20C1, 'X'),
(0x20D0, 'V'),
(0x20F1, 'X'),
(0x2100, '3', 'a/c'),
(0x2101, '3', 'a/s'),
- ]
-
-def _seg_22():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x2102, 'M', 'c'),
(0x2103, 'M', '°c'),
(0x2104, 'V'),
+ ]
+
+def _seg_22() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x2105, '3', 'c/o'),
(0x2106, '3', 'c/u'),
(0x2107, 'M', 'ɛ'),
@@ -2419,14 +2395,13 @@ def _seg_22():
(0x2177, 'M', 'viii'),
(0x2178, 'M', 'ix'),
(0x2179, 'M', 'x'),
- ]
-
-def _seg_23():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x217A, 'M', 'xi'),
(0x217B, 'M', 'xii'),
(0x217C, 'M', 'l'),
+ ]
+
+def _seg_23() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x217D, 'M', 'c'),
(0x217E, 'M', 'd'),
(0x217F, 'M', 'm'),
@@ -2524,14 +2499,13 @@ def _seg_23():
(0x24B7, 'M', 'b'),
(0x24B8, 'M', 'c'),
(0x24B9, 'M', 'd'),
- ]
-
-def _seg_24():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x24BA, 'M', 'e'),
(0x24BB, 'M', 'f'),
(0x24BC, 'M', 'g'),
+ ]
+
+def _seg_24() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x24BD, 'M', 'h'),
(0x24BE, 'M', 'i'),
(0x24BF, 'M', 'j'),
@@ -2629,23 +2603,21 @@ def _seg_24():
(0x2C23, 'M', 'ⱓ'),
(0x2C24, 'M', 'ⱔ'),
(0x2C25, 'M', 'ⱕ'),
- ]
-
-def _seg_25():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x2C26, 'M', 'ⱖ'),
(0x2C27, 'M', 'ⱗ'),
(0x2C28, 'M', 'ⱘ'),
+ ]
+
+def _seg_25() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x2C29, 'M', 'ⱙ'),
(0x2C2A, 'M', 'ⱚ'),
(0x2C2B, 'M', 'ⱛ'),
(0x2C2C, 'M', 'ⱜ'),
(0x2C2D, 'M', 'ⱝ'),
(0x2C2E, 'M', 'ⱞ'),
- (0x2C2F, 'X'),
+ (0x2C2F, 'M', 'ⱟ'),
(0x2C30, 'V'),
- (0x2C5F, 'X'),
(0x2C60, 'M', 'ⱡ'),
(0x2C61, 'V'),
(0x2C62, 'M', 'ɫ'),
@@ -2734,15 +2706,14 @@ def _seg_25():
(0x2CBC, 'M', 'ⲽ'),
(0x2CBD, 'V'),
(0x2CBE, 'M', 'ⲿ'),
- ]
-
-def _seg_26():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x2CBF, 'V'),
(0x2CC0, 'M', 'ⳁ'),
(0x2CC1, 'V'),
(0x2CC2, 'M', 'ⳃ'),
+ ]
+
+def _seg_26() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x2CC3, 'V'),
(0x2CC4, 'M', 'ⳅ'),
(0x2CC5, 'V'),
@@ -2813,7 +2784,7 @@ def _seg_26():
(0x2DD8, 'V'),
(0x2DDF, 'X'),
(0x2DE0, 'V'),
- (0x2E53, 'X'),
+ (0x2E5E, 'X'),
(0x2E80, 'V'),
(0x2E9A, 'X'),
(0x2E9B, 'V'),
@@ -2839,15 +2810,14 @@ def _seg_26():
(0x2F0F, 'M', '几'),
(0x2F10, 'M', '凵'),
(0x2F11, 'M', '刀'),
- ]
-
-def _seg_27():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x2F12, 'M', '力'),
(0x2F13, 'M', '勹'),
(0x2F14, 'M', '匕'),
(0x2F15, 'M', '匚'),
+ ]
+
+def _seg_27() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x2F16, 'M', '匸'),
(0x2F17, 'M', '十'),
(0x2F18, 'M', '卜'),
@@ -2944,15 +2914,14 @@ def _seg_27():
(0x2F73, 'M', '穴'),
(0x2F74, 'M', '立'),
(0x2F75, 'M', '竹'),
- ]
-
-def _seg_28():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x2F76, 'M', '米'),
(0x2F77, 'M', '糸'),
(0x2F78, 'M', '缶'),
(0x2F79, 'M', '网'),
+ ]
+
+def _seg_28() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x2F7A, 'M', '羊'),
(0x2F7B, 'M', '羽'),
(0x2F7C, 'M', '老'),
@@ -3049,15 +3018,14 @@ def _seg_28():
(0x3000, '3', ' '),
(0x3001, 'V'),
(0x3002, 'M', '.'),
- ]
-
-def _seg_29():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x3003, 'V'),
(0x3036, 'M', '〒'),
(0x3037, 'V'),
(0x3038, 'M', '十'),
+ ]
+
+def _seg_29() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x3039, 'M', '卄'),
(0x303A, 'M', '卅'),
(0x303B, 'V'),
@@ -3154,15 +3122,14 @@ def _seg_29():
(0x317E, 'M', 'ᄶ'),
(0x317F, 'M', 'ᅀ'),
(0x3180, 'M', 'ᅇ'),
- ]
-
-def _seg_30():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x3181, 'M', 'ᅌ'),
(0x3182, 'M', 'ᇱ'),
(0x3183, 'M', 'ᇲ'),
(0x3184, 'M', 'ᅗ'),
+ ]
+
+def _seg_30() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x3185, 'M', 'ᅘ'),
(0x3186, 'M', 'ᅙ'),
(0x3187, 'M', 'ᆄ'),
@@ -3259,15 +3226,14 @@ def _seg_30():
(0x3240, '3', '(祭)'),
(0x3241, '3', '(休)'),
(0x3242, '3', '(自)'),
- ]
-
-def _seg_31():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x3243, '3', '(至)'),
(0x3244, 'M', '問'),
(0x3245, 'M', '幼'),
(0x3246, 'M', '文'),
+ ]
+
+def _seg_31() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x3247, 'M', '箏'),
(0x3248, 'V'),
(0x3250, 'M', 'pte'),
@@ -3364,15 +3330,14 @@ def _seg_31():
(0x32AB, 'M', '学'),
(0x32AC, 'M', '監'),
(0x32AD, 'M', '企'),
- ]
-
-def _seg_32():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x32AE, 'M', '資'),
(0x32AF, 'M', '協'),
(0x32B0, 'M', '夜'),
(0x32B1, 'M', '36'),
+ ]
+
+def _seg_32() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x32B2, 'M', '37'),
(0x32B3, 'M', '38'),
(0x32B4, 'M', '39'),
@@ -3469,15 +3434,14 @@ def _seg_32():
(0x330F, 'M', 'ガンマ'),
(0x3310, 'M', 'ギガ'),
(0x3311, 'M', 'ギニー'),
- ]
-
-def _seg_33():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x3312, 'M', 'キュリー'),
(0x3313, 'M', 'ギルダー'),
(0x3314, 'M', 'キロ'),
(0x3315, 'M', 'キログラム'),
+ ]
+
+def _seg_33() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x3316, 'M', 'キロメートル'),
(0x3317, 'M', 'キロワット'),
(0x3318, 'M', 'グラム'),
@@ -3574,15 +3538,14 @@ def _seg_33():
(0x3373, 'M', 'au'),
(0x3374, 'M', 'bar'),
(0x3375, 'M', 'ov'),
- ]
-
-def _seg_34():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x3376, 'M', 'pc'),
(0x3377, 'M', 'dm'),
(0x3378, 'M', 'dm2'),
(0x3379, 'M', 'dm3'),
+ ]
+
+def _seg_34() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x337A, 'M', 'iu'),
(0x337B, 'M', '平成'),
(0x337C, 'M', '昭和'),
@@ -3679,15 +3642,14 @@ def _seg_34():
(0x33D7, 'M', 'ph'),
(0x33D8, 'X'),
(0x33D9, 'M', 'ppm'),
- ]
-
-def _seg_35():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x33DA, 'M', 'pr'),
(0x33DB, 'M', 'sr'),
(0x33DC, 'M', 'sv'),
(0x33DD, 'M', 'wb'),
+ ]
+
+def _seg_35() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x33DE, 'M', 'v∕m'),
(0x33DF, 'M', 'a∕m'),
(0x33E0, 'M', '1日'),
@@ -3723,8 +3685,6 @@ def _seg_35():
(0x33FE, 'M', '31日'),
(0x33FF, 'M', 'gal'),
(0x3400, 'V'),
- (0x9FFD, 'X'),
- (0xA000, 'V'),
(0xA48D, 'X'),
(0xA490, 'V'),
(0xA4C7, 'X'),
@@ -3784,17 +3744,16 @@ def _seg_35():
(0xA685, 'V'),
(0xA686, 'M', 'ꚇ'),
(0xA687, 'V'),
- ]
-
-def _seg_36():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xA688, 'M', 'ꚉ'),
(0xA689, 'V'),
(0xA68A, 'M', 'ꚋ'),
(0xA68B, 'V'),
(0xA68C, 'M', 'ꚍ'),
(0xA68D, 'V'),
+ ]
+
+def _seg_36() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xA68E, 'M', 'ꚏ'),
(0xA68F, 'V'),
(0xA690, 'M', 'ꚑ'),
@@ -3889,17 +3848,16 @@ def _seg_36():
(0xA76C, 'M', 'ꝭ'),
(0xA76D, 'V'),
(0xA76E, 'M', 'ꝯ'),
- ]
-
-def _seg_37():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xA76F, 'V'),
(0xA770, 'M', 'ꝯ'),
(0xA771, 'V'),
(0xA779, 'M', 'ꝺ'),
(0xA77A, 'V'),
(0xA77B, 'M', 'ꝼ'),
+ ]
+
+def _seg_37() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xA77C, 'V'),
(0xA77D, 'M', 'ᵹ'),
(0xA77E, 'M', 'ꝿ'),
@@ -3962,7 +3920,8 @@ def _seg_37():
(0xA7BD, 'V'),
(0xA7BE, 'M', 'ꞿ'),
(0xA7BF, 'V'),
- (0xA7C0, 'X'),
+ (0xA7C0, 'M', 'ꟁ'),
+ (0xA7C1, 'V'),
(0xA7C2, 'M', 'ꟃ'),
(0xA7C3, 'V'),
(0xA7C4, 'M', 'ꞔ'),
@@ -3973,6 +3932,20 @@ def _seg_37():
(0xA7C9, 'M', 'ꟊ'),
(0xA7CA, 'V'),
(0xA7CB, 'X'),
+ (0xA7D0, 'M', 'ꟑ'),
+ (0xA7D1, 'V'),
+ (0xA7D2, 'X'),
+ (0xA7D3, 'V'),
+ (0xA7D4, 'X'),
+ (0xA7D5, 'V'),
+ (0xA7D6, 'M', 'ꟗ'),
+ (0xA7D7, 'V'),
+ (0xA7D8, 'M', 'ꟙ'),
+ (0xA7D9, 'V'),
+ (0xA7DA, 'X'),
+ (0xA7F2, 'M', 'c'),
+ (0xA7F3, 'M', 'f'),
+ (0xA7F4, 'M', 'q'),
(0xA7F5, 'M', 'ꟶ'),
(0xA7F6, 'V'),
(0xA7F8, 'M', 'ħ'),
@@ -3985,6 +3958,10 @@ def _seg_37():
(0xA878, 'X'),
(0xA880, 'V'),
(0xA8C6, 'X'),
+ ]
+
+def _seg_38() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xA8CE, 'V'),
(0xA8DA, 'X'),
(0xA8E0, 'V'),
@@ -3994,11 +3971,6 @@ def _seg_37():
(0xA980, 'V'),
(0xA9CE, 'X'),
(0xA9CF, 'V'),
- ]
-
-def _seg_38():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xA9DA, 'X'),
(0xA9DE, 'V'),
(0xA9FF, 'X'),
@@ -4090,6 +4062,10 @@ def _seg_38():
(0xABA8, 'M', 'Ꮨ'),
(0xABA9, 'M', 'Ꮩ'),
(0xABAA, 'M', 'Ꮪ'),
+ ]
+
+def _seg_39() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xABAB, 'M', 'Ꮫ'),
(0xABAC, 'M', 'Ꮬ'),
(0xABAD, 'M', 'Ꮭ'),
@@ -4099,11 +4075,6 @@ def _seg_38():
(0xABB1, 'M', 'Ꮱ'),
(0xABB2, 'M', 'Ꮲ'),
(0xABB3, 'M', 'Ꮳ'),
- ]
-
-def _seg_39():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xABB4, 'M', 'Ꮴ'),
(0xABB5, 'M', 'Ꮵ'),
(0xABB6, 'M', 'Ꮶ'),
@@ -4195,6 +4166,10 @@ def _seg_39():
(0xF943, 'M', '弄'),
(0xF944, 'M', '籠'),
(0xF945, 'M', '聾'),
+ ]
+
+def _seg_40() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xF946, 'M', '牢'),
(0xF947, 'M', '磊'),
(0xF948, 'M', '賂'),
@@ -4204,11 +4179,6 @@ def _seg_39():
(0xF94C, 'M', '樓'),
(0xF94D, 'M', '淚'),
(0xF94E, 'M', '漏'),
- ]
-
-def _seg_40():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xF94F, 'M', '累'),
(0xF950, 'M', '縷'),
(0xF951, 'M', '陋'),
@@ -4300,6 +4270,10 @@ def _seg_40():
(0xF9A7, 'M', '獵'),
(0xF9A8, 'M', '令'),
(0xF9A9, 'M', '囹'),
+ ]
+
+def _seg_41() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xF9AA, 'M', '寧'),
(0xF9AB, 'M', '嶺'),
(0xF9AC, 'M', '怜'),
@@ -4309,11 +4283,6 @@ def _seg_40():
(0xF9B0, 'M', '聆'),
(0xF9B1, 'M', '鈴'),
(0xF9B2, 'M', '零'),
- ]
-
-def _seg_41():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xF9B3, 'M', '靈'),
(0xF9B4, 'M', '領'),
(0xF9B5, 'M', '例'),
@@ -4405,6 +4374,10 @@ def _seg_41():
(0xFA0B, 'M', '廓'),
(0xFA0C, 'M', '兀'),
(0xFA0D, 'M', '嗀'),
+ ]
+
+def _seg_42() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xFA0E, 'V'),
(0xFA10, 'M', '塚'),
(0xFA11, 'V'),
@@ -4414,11 +4387,6 @@ def _seg_41():
(0xFA16, 'M', '猪'),
(0xFA17, 'M', '益'),
(0xFA18, 'M', '礼'),
- ]
-
-def _seg_42():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xFA19, 'M', '神'),
(0xFA1A, 'M', '祥'),
(0xFA1B, 'M', '福'),
@@ -4510,6 +4478,10 @@ def _seg_42():
(0xFA76, 'M', '勇'),
(0xFA77, 'M', '勺'),
(0xFA78, 'M', '喝'),
+ ]
+
+def _seg_43() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xFA79, 'M', '啕'),
(0xFA7A, 'M', '喙'),
(0xFA7B, 'M', '嗢'),
@@ -4519,11 +4491,6 @@ def _seg_42():
(0xFA7F, 'M', '奔'),
(0xFA80, 'M', '婢'),
(0xFA81, 'M', '嬨'),
- ]
-
-def _seg_43():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xFA82, 'M', '廒'),
(0xFA83, 'M', '廙'),
(0xFA84, 'M', '彩'),
@@ -4615,6 +4582,10 @@ def _seg_43():
(0xFADA, 'X'),
(0xFB00, 'M', 'ff'),
(0xFB01, 'M', 'fi'),
+ ]
+
+def _seg_44() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xFB02, 'M', 'fl'),
(0xFB03, 'M', 'ffi'),
(0xFB04, 'M', 'ffl'),
@@ -4624,11 +4595,6 @@ def _seg_43():
(0xFB14, 'M', 'մե'),
(0xFB15, 'M', 'մի'),
(0xFB16, 'M', 'վն'),
- ]
-
-def _seg_44():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xFB17, 'M', 'մխ'),
(0xFB18, 'X'),
(0xFB1D, 'M', 'יִ'),
@@ -4713,13 +4679,17 @@ def _seg_44():
(0xFBAE, 'M', 'ے'),
(0xFBB0, 'M', 'ۓ'),
(0xFBB2, 'V'),
- (0xFBC2, 'X'),
+ (0xFBC3, 'X'),
(0xFBD3, 'M', 'ڭ'),
(0xFBD7, 'M', 'ۇ'),
(0xFBD9, 'M', 'ۆ'),
(0xFBDB, 'M', 'ۈ'),
(0xFBDD, 'M', 'ۇٴ'),
(0xFBDE, 'M', 'ۋ'),
+ ]
+
+def _seg_45() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xFBE0, 'M', 'ۅ'),
(0xFBE2, 'M', 'ۉ'),
(0xFBE4, 'M', 'ې'),
@@ -4729,11 +4699,6 @@ def _seg_44():
(0xFBEE, 'M', 'ئو'),
(0xFBF0, 'M', 'ئۇ'),
(0xFBF2, 'M', 'ئۆ'),
- ]
-
-def _seg_45():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xFBF4, 'M', 'ئۈ'),
(0xFBF6, 'M', 'ئې'),
(0xFBF9, 'M', 'ئى'),
@@ -4825,6 +4790,10 @@ def _seg_45():
(0xFC54, 'M', 'هي'),
(0xFC55, 'M', 'يج'),
(0xFC56, 'M', 'يح'),
+ ]
+
+def _seg_46() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xFC57, 'M', 'يخ'),
(0xFC58, 'M', 'يم'),
(0xFC59, 'M', 'يى'),
@@ -4834,11 +4803,6 @@ def _seg_45():
(0xFC5D, 'M', 'ىٰ'),
(0xFC5E, '3', ' ٌّ'),
(0xFC5F, '3', ' ٍّ'),
- ]
-
-def _seg_46():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xFC60, '3', ' َّ'),
(0xFC61, '3', ' ُّ'),
(0xFC62, '3', ' ِّ'),
@@ -4930,6 +4894,10 @@ def _seg_46():
(0xFCB8, 'M', 'طح'),
(0xFCB9, 'M', 'ظم'),
(0xFCBA, 'M', 'عج'),
+ ]
+
+def _seg_47() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xFCBB, 'M', 'عم'),
(0xFCBC, 'M', 'غج'),
(0xFCBD, 'M', 'غم'),
@@ -4939,11 +4907,6 @@ def _seg_46():
(0xFCC1, 'M', 'فم'),
(0xFCC2, 'M', 'قح'),
(0xFCC3, 'M', 'قم'),
- ]
-
-def _seg_47():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xFCC4, 'M', 'كج'),
(0xFCC5, 'M', 'كح'),
(0xFCC6, 'M', 'كخ'),
@@ -5035,6 +4998,10 @@ def _seg_47():
(0xFD1C, 'M', 'حي'),
(0xFD1D, 'M', 'جى'),
(0xFD1E, 'M', 'جي'),
+ ]
+
+def _seg_48() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xFD1F, 'M', 'خى'),
(0xFD20, 'M', 'خي'),
(0xFD21, 'M', 'صى'),
@@ -5044,11 +5011,6 @@ def _seg_47():
(0xFD25, 'M', 'شج'),
(0xFD26, 'M', 'شح'),
(0xFD27, 'M', 'شخ'),
- ]
-
-def _seg_48():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xFD28, 'M', 'شم'),
(0xFD29, 'M', 'شر'),
(0xFD2A, 'M', 'سر'),
@@ -5071,7 +5033,6 @@ def _seg_48():
(0xFD3B, 'M', 'ظم'),
(0xFD3C, 'M', 'اً'),
(0xFD3E, 'V'),
- (0xFD40, 'X'),
(0xFD50, 'M', 'تجم'),
(0xFD51, 'M', 'تحج'),
(0xFD53, 'M', 'تحم'),
@@ -5141,6 +5102,10 @@ def _seg_48():
(0xFDA4, 'M', 'تمى'),
(0xFDA5, 'M', 'جمي'),
(0xFDA6, 'M', 'جحى'),
+ ]
+
+def _seg_49() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xFDA7, 'M', 'جمى'),
(0xFDA8, 'M', 'سخى'),
(0xFDA9, 'M', 'صحي'),
@@ -5149,11 +5114,6 @@ def _seg_48():
(0xFDAC, 'M', 'لجي'),
(0xFDAD, 'M', 'لمي'),
(0xFDAE, 'M', 'يحي'),
- ]
-
-def _seg_49():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xFDAF, 'M', 'يجي'),
(0xFDB0, 'M', 'يمي'),
(0xFDB1, 'M', 'ممي'),
@@ -5180,6 +5140,8 @@ def _seg_49():
(0xFDC6, 'M', 'سخي'),
(0xFDC7, 'M', 'نجي'),
(0xFDC8, 'X'),
+ (0xFDCF, 'V'),
+ (0xFDD0, 'X'),
(0xFDF0, 'M', 'صلے'),
(0xFDF1, 'M', 'قلے'),
(0xFDF2, 'M', 'الله'),
@@ -5194,7 +5156,6 @@ def _seg_49():
(0xFDFB, '3', 'جل جلاله'),
(0xFDFC, 'M', 'ریال'),
(0xFDFD, 'V'),
- (0xFDFE, 'X'),
(0xFE00, 'I'),
(0xFE10, '3', ','),
(0xFE11, 'M', '、'),
@@ -5245,6 +5206,10 @@ def _seg_49():
(0xFE5B, '3', '{'),
(0xFE5C, '3', '}'),
(0xFE5D, 'M', '〔'),
+ ]
+
+def _seg_50() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xFE5E, 'M', '〕'),
(0xFE5F, '3', '#'),
(0xFE60, '3', '&'),
@@ -5254,11 +5219,6 @@ def _seg_49():
(0xFE64, '3', '<'),
(0xFE65, '3', '>'),
(0xFE66, '3', '='),
- ]
-
-def _seg_50():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xFE67, 'X'),
(0xFE68, '3', '\\'),
(0xFE69, '3', '$'),
@@ -5350,6 +5310,10 @@ def _seg_50():
(0xFF18, 'M', '8'),
(0xFF19, 'M', '9'),
(0xFF1A, '3', ':'),
+ ]
+
+def _seg_51() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xFF1B, '3', ';'),
(0xFF1C, '3', '<'),
(0xFF1D, '3', '='),
@@ -5359,11 +5323,6 @@ def _seg_50():
(0xFF21, 'M', 'a'),
(0xFF22, 'M', 'b'),
(0xFF23, 'M', 'c'),
- ]
-
-def _seg_51():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xFF24, 'M', 'd'),
(0xFF25, 'M', 'e'),
(0xFF26, 'M', 'f'),
@@ -5455,6 +5414,10 @@ def _seg_51():
(0xFF7C, 'M', 'シ'),
(0xFF7D, 'M', 'ス'),
(0xFF7E, 'M', 'セ'),
+ ]
+
+def _seg_52() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xFF7F, 'M', 'ソ'),
(0xFF80, 'M', 'タ'),
(0xFF81, 'M', 'チ'),
@@ -5464,11 +5427,6 @@ def _seg_51():
(0xFF85, 'M', 'ナ'),
(0xFF86, 'M', 'ニ'),
(0xFF87, 'M', 'ヌ'),
- ]
-
-def _seg_52():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0xFF88, 'M', 'ネ'),
(0xFF89, 'M', 'ノ'),
(0xFF8A, 'M', 'ハ'),
@@ -5560,6 +5518,10 @@ def _seg_52():
(0xFFE7, 'X'),
(0xFFE8, 'M', '│'),
(0xFFE9, 'M', '←'),
+ ]
+
+def _seg_53() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0xFFEA, 'M', '↑'),
(0xFFEB, 'M', '→'),
(0xFFEC, 'M', '↓'),
@@ -5569,11 +5531,6 @@ def _seg_52():
(0x10000, 'V'),
(0x1000C, 'X'),
(0x1000D, 'V'),
- ]
-
-def _seg_53():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x10027, 'X'),
(0x10028, 'V'),
(0x1003B, 'X'),
@@ -5665,6 +5622,10 @@ def _seg_53():
(0x104B3, 'M', '𐓛'),
(0x104B4, 'M', '𐓜'),
(0x104B5, 'M', '𐓝'),
+ ]
+
+def _seg_54() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x104B6, 'M', '𐓞'),
(0x104B7, 'M', '𐓟'),
(0x104B8, 'M', '𐓠'),
@@ -5674,11 +5635,6 @@ def _seg_53():
(0x104BC, 'M', '𐓤'),
(0x104BD, 'M', '𐓥'),
(0x104BE, 'M', '𐓦'),
- ]
-
-def _seg_54():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x104BF, 'M', '𐓧'),
(0x104C0, 'M', '𐓨'),
(0x104C1, 'M', '𐓩'),
@@ -5708,13 +5664,123 @@ def _seg_54():
(0x10530, 'V'),
(0x10564, 'X'),
(0x1056F, 'V'),
- (0x10570, 'X'),
+ (0x10570, 'M', '𐖗'),
+ (0x10571, 'M', '𐖘'),
+ (0x10572, 'M', '𐖙'),
+ (0x10573, 'M', '𐖚'),
+ (0x10574, 'M', '𐖛'),
+ (0x10575, 'M', '𐖜'),
+ (0x10576, 'M', '𐖝'),
+ (0x10577, 'M', '𐖞'),
+ (0x10578, 'M', '𐖟'),
+ (0x10579, 'M', '𐖠'),
+ (0x1057A, 'M', '𐖡'),
+ (0x1057B, 'X'),
+ (0x1057C, 'M', '𐖣'),
+ (0x1057D, 'M', '𐖤'),
+ (0x1057E, 'M', '𐖥'),
+ (0x1057F, 'M', '𐖦'),
+ (0x10580, 'M', '𐖧'),
+ (0x10581, 'M', '𐖨'),
+ (0x10582, 'M', '𐖩'),
+ (0x10583, 'M', '𐖪'),
+ (0x10584, 'M', '𐖫'),
+ (0x10585, 'M', '𐖬'),
+ (0x10586, 'M', '𐖭'),
+ (0x10587, 'M', '𐖮'),
+ (0x10588, 'M', '𐖯'),
+ (0x10589, 'M', '𐖰'),
+ (0x1058A, 'M', '𐖱'),
+ (0x1058B, 'X'),
+ (0x1058C, 'M', '𐖳'),
+ (0x1058D, 'M', '𐖴'),
+ (0x1058E, 'M', '𐖵'),
+ (0x1058F, 'M', '𐖶'),
+ (0x10590, 'M', '𐖷'),
+ (0x10591, 'M', '𐖸'),
+ (0x10592, 'M', '𐖹'),
+ (0x10593, 'X'),
+ (0x10594, 'M', '𐖻'),
+ (0x10595, 'M', '𐖼'),
+ (0x10596, 'X'),
+ (0x10597, 'V'),
+ (0x105A2, 'X'),
+ (0x105A3, 'V'),
+ (0x105B2, 'X'),
+ (0x105B3, 'V'),
+ (0x105BA, 'X'),
+ (0x105BB, 'V'),
+ (0x105BD, 'X'),
(0x10600, 'V'),
(0x10737, 'X'),
(0x10740, 'V'),
(0x10756, 'X'),
(0x10760, 'V'),
(0x10768, 'X'),
+ (0x10780, 'V'),
+ (0x10781, 'M', 'ː'),
+ (0x10782, 'M', 'ˑ'),
+ (0x10783, 'M', 'æ'),
+ (0x10784, 'M', 'ʙ'),
+ (0x10785, 'M', 'ɓ'),
+ (0x10786, 'X'),
+ (0x10787, 'M', 'ʣ'),
+ (0x10788, 'M', 'ꭦ'),
+ ]
+
+def _seg_55() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x10789, 'M', 'ʥ'),
+ (0x1078A, 'M', 'ʤ'),
+ (0x1078B, 'M', 'ɖ'),
+ (0x1078C, 'M', 'ɗ'),
+ (0x1078D, 'M', 'ᶑ'),
+ (0x1078E, 'M', 'ɘ'),
+ (0x1078F, 'M', 'ɞ'),
+ (0x10790, 'M', 'ʩ'),
+ (0x10791, 'M', 'ɤ'),
+ (0x10792, 'M', 'ɢ'),
+ (0x10793, 'M', 'ɠ'),
+ (0x10794, 'M', 'ʛ'),
+ (0x10795, 'M', 'ħ'),
+ (0x10796, 'M', 'ʜ'),
+ (0x10797, 'M', 'ɧ'),
+ (0x10798, 'M', 'ʄ'),
+ (0x10799, 'M', 'ʪ'),
+ (0x1079A, 'M', 'ʫ'),
+ (0x1079B, 'M', 'ɬ'),
+ (0x1079C, 'M', '𝼄'),
+ (0x1079D, 'M', 'ꞎ'),
+ (0x1079E, 'M', 'ɮ'),
+ (0x1079F, 'M', '𝼅'),
+ (0x107A0, 'M', 'ʎ'),
+ (0x107A1, 'M', '𝼆'),
+ (0x107A2, 'M', 'ø'),
+ (0x107A3, 'M', 'ɶ'),
+ (0x107A4, 'M', 'ɷ'),
+ (0x107A5, 'M', 'q'),
+ (0x107A6, 'M', 'ɺ'),
+ (0x107A7, 'M', '𝼈'),
+ (0x107A8, 'M', 'ɽ'),
+ (0x107A9, 'M', 'ɾ'),
+ (0x107AA, 'M', 'ʀ'),
+ (0x107AB, 'M', 'ʨ'),
+ (0x107AC, 'M', 'ʦ'),
+ (0x107AD, 'M', 'ꭧ'),
+ (0x107AE, 'M', 'ʧ'),
+ (0x107AF, 'M', 'ʈ'),
+ (0x107B0, 'M', 'ⱱ'),
+ (0x107B1, 'X'),
+ (0x107B2, 'M', 'ʏ'),
+ (0x107B3, 'M', 'ʡ'),
+ (0x107B4, 'M', 'ʢ'),
+ (0x107B5, 'M', 'ʘ'),
+ (0x107B6, 'M', 'ǀ'),
+ (0x107B7, 'M', 'ǁ'),
+ (0x107B8, 'M', 'ǂ'),
+ (0x107B9, 'M', '𝼊'),
+ (0x107BA, 'M', '𝼞'),
+ (0x107BB, 'X'),
(0x10800, 'V'),
(0x10806, 'X'),
(0x10808, 'V'),
@@ -5764,6 +5830,10 @@ def _seg_54():
(0x10A60, 'V'),
(0x10AA0, 'X'),
(0x10AC0, 'V'),
+ ]
+
+def _seg_56() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x10AE7, 'X'),
(0x10AEB, 'V'),
(0x10AF7, 'X'),
@@ -5779,11 +5849,6 @@ def _seg_54():
(0x10B9D, 'X'),
(0x10BA9, 'V'),
(0x10BB0, 'X'),
- ]
-
-def _seg_55():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x10C00, 'V'),
(0x10C49, 'X'),
(0x10C80, 'M', '𐳀'),
@@ -5852,10 +5917,12 @@ def _seg_55():
(0x10EAE, 'X'),
(0x10EB0, 'V'),
(0x10EB2, 'X'),
- (0x10F00, 'V'),
+ (0x10EFD, 'V'),
(0x10F28, 'X'),
(0x10F30, 'V'),
(0x10F5A, 'X'),
+ (0x10F70, 'V'),
+ (0x10F8A, 'X'),
(0x10FB0, 'V'),
(0x10FCC, 'X'),
(0x10FE0, 'V'),
@@ -5863,11 +5930,15 @@ def _seg_55():
(0x11000, 'V'),
(0x1104E, 'X'),
(0x11052, 'V'),
- (0x11070, 'X'),
+ (0x11076, 'X'),
(0x1107F, 'V'),
(0x110BD, 'X'),
(0x110BE, 'V'),
- (0x110C2, 'X'),
+ ]
+
+def _seg_57() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x110C3, 'X'),
(0x110D0, 'V'),
(0x110E9, 'X'),
(0x110F0, 'V'),
@@ -5884,13 +5955,8 @@ def _seg_55():
(0x111F5, 'X'),
(0x11200, 'V'),
(0x11212, 'X'),
- ]
-
-def _seg_56():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x11213, 'V'),
- (0x1123F, 'X'),
+ (0x11242, 'X'),
(0x11280, 'V'),
(0x11287, 'X'),
(0x11288, 'V'),
@@ -5954,7 +6020,7 @@ def _seg_56():
(0x11660, 'V'),
(0x1166D, 'X'),
(0x11680, 'V'),
- (0x116B9, 'X'),
+ (0x116BA, 'X'),
(0x116C0, 'V'),
(0x116CA, 'X'),
(0x11700, 'V'),
@@ -5962,7 +6028,7 @@ def _seg_56():
(0x1171D, 'V'),
(0x1172C, 'X'),
(0x11730, 'V'),
- (0x11740, 'X'),
+ (0x11747, 'X'),
(0x11800, 'V'),
(0x1183C, 'X'),
(0x118A0, 'M', '𑣀'),
@@ -5972,6 +6038,10 @@ def _seg_56():
(0x118A4, 'M', '𑣄'),
(0x118A5, 'M', '𑣅'),
(0x118A6, 'M', '𑣆'),
+ ]
+
+def _seg_58() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x118A7, 'M', '𑣇'),
(0x118A8, 'M', '𑣈'),
(0x118A9, 'M', '𑣉'),
@@ -5989,11 +6059,6 @@ def _seg_56():
(0x118B5, 'M', '𑣕'),
(0x118B6, 'M', '𑣖'),
(0x118B7, 'M', '𑣗'),
- ]
-
-def _seg_57():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x118B8, 'M', '𑣘'),
(0x118B9, 'M', '𑣙'),
(0x118BA, 'M', '𑣚'),
@@ -6030,8 +6095,10 @@ def _seg_57():
(0x11A48, 'X'),
(0x11A50, 'V'),
(0x11AA3, 'X'),
- (0x11AC0, 'V'),
+ (0x11AB0, 'V'),
(0x11AF9, 'X'),
+ (0x11B00, 'V'),
+ (0x11B0A, 'X'),
(0x11C00, 'V'),
(0x11C09, 'X'),
(0x11C0A, 'V'),
@@ -6074,6 +6141,16 @@ def _seg_57():
(0x11DAA, 'X'),
(0x11EE0, 'V'),
(0x11EF9, 'X'),
+ (0x11F00, 'V'),
+ ]
+
+def _seg_59() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x11F11, 'X'),
+ (0x11F12, 'V'),
+ (0x11F3B, 'X'),
+ (0x11F3E, 'V'),
+ (0x11F5A, 'X'),
(0x11FB0, 'V'),
(0x11FB1, 'X'),
(0x11FC0, 'V'),
@@ -6086,23 +6163,24 @@ def _seg_57():
(0x12475, 'X'),
(0x12480, 'V'),
(0x12544, 'X'),
+ (0x12F90, 'V'),
+ (0x12FF3, 'X'),
(0x13000, 'V'),
- (0x1342F, 'X'),
+ (0x13430, 'X'),
+ (0x13440, 'V'),
+ (0x13456, 'X'),
(0x14400, 'V'),
(0x14647, 'X'),
(0x16800, 'V'),
(0x16A39, 'X'),
(0x16A40, 'V'),
(0x16A5F, 'X'),
- ]
-
-def _seg_58():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x16A60, 'V'),
(0x16A6A, 'X'),
(0x16A6E, 'V'),
- (0x16A70, 'X'),
+ (0x16ABF, 'X'),
+ (0x16AC0, 'V'),
+ (0x16ACA, 'X'),
(0x16AD0, 'V'),
(0x16AEE, 'X'),
(0x16AF0, 'V'),
@@ -6167,10 +6245,24 @@ def _seg_58():
(0x18CD6, 'X'),
(0x18D00, 'V'),
(0x18D09, 'X'),
+ (0x1AFF0, 'V'),
+ ]
+
+def _seg_60() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1AFF4, 'X'),
+ (0x1AFF5, 'V'),
+ (0x1AFFC, 'X'),
+ (0x1AFFD, 'V'),
+ (0x1AFFF, 'X'),
(0x1B000, 'V'),
- (0x1B11F, 'X'),
+ (0x1B123, 'X'),
+ (0x1B132, 'V'),
+ (0x1B133, 'X'),
(0x1B150, 'V'),
(0x1B153, 'X'),
+ (0x1B155, 'V'),
+ (0x1B156, 'X'),
(0x1B164, 'V'),
(0x1B168, 'X'),
(0x1B170, 'V'),
@@ -6186,6 +6278,12 @@ def _seg_58():
(0x1BC9C, 'V'),
(0x1BCA0, 'I'),
(0x1BCA4, 'X'),
+ (0x1CF00, 'V'),
+ (0x1CF2E, 'X'),
+ (0x1CF30, 'V'),
+ (0x1CF47, 'X'),
+ (0x1CF50, 'V'),
+ (0x1CFC4, 'X'),
(0x1D000, 'V'),
(0x1D0F6, 'X'),
(0x1D100, 'V'),
@@ -6199,11 +6297,6 @@ def _seg_58():
(0x1D163, 'M', '𝅘𝅥𝅱'),
(0x1D164, 'M', '𝅘𝅥𝅲'),
(0x1D165, 'V'),
- ]
-
-def _seg_59():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1D173, 'X'),
(0x1D17B, 'V'),
(0x1D1BB, 'M', '𝆹𝅥'),
@@ -6213,9 +6306,11 @@ def _seg_59():
(0x1D1BF, 'M', '𝆹𝅥𝅯'),
(0x1D1C0, 'M', '𝆺𝅥𝅯'),
(0x1D1C1, 'V'),
- (0x1D1E9, 'X'),
+ (0x1D1EB, 'X'),
(0x1D200, 'V'),
(0x1D246, 'X'),
+ (0x1D2C0, 'V'),
+ (0x1D2D4, 'X'),
(0x1D2E0, 'V'),
(0x1D2F4, 'X'),
(0x1D300, 'V'),
@@ -6255,6 +6350,10 @@ def _seg_59():
(0x1D41E, 'M', 'e'),
(0x1D41F, 'M', 'f'),
(0x1D420, 'M', 'g'),
+ ]
+
+def _seg_61() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1D421, 'M', 'h'),
(0x1D422, 'M', 'i'),
(0x1D423, 'M', 'j'),
@@ -6304,11 +6403,6 @@ def _seg_59():
(0x1D44F, 'M', 'b'),
(0x1D450, 'M', 'c'),
(0x1D451, 'M', 'd'),
- ]
-
-def _seg_60():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1D452, 'M', 'e'),
(0x1D453, 'M', 'f'),
(0x1D454, 'M', 'g'),
@@ -6360,6 +6454,10 @@ def _seg_60():
(0x1D482, 'M', 'a'),
(0x1D483, 'M', 'b'),
(0x1D484, 'M', 'c'),
+ ]
+
+def _seg_62() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1D485, 'M', 'd'),
(0x1D486, 'M', 'e'),
(0x1D487, 'M', 'f'),
@@ -6409,11 +6507,6 @@ def _seg_60():
(0x1D4B6, 'M', 'a'),
(0x1D4B7, 'M', 'b'),
(0x1D4B8, 'M', 'c'),
- ]
-
-def _seg_61():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1D4B9, 'M', 'd'),
(0x1D4BA, 'X'),
(0x1D4BB, 'M', 'f'),
@@ -6465,6 +6558,10 @@ def _seg_61():
(0x1D4E9, 'M', 'z'),
(0x1D4EA, 'M', 'a'),
(0x1D4EB, 'M', 'b'),
+ ]
+
+def _seg_63() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1D4EC, 'M', 'c'),
(0x1D4ED, 'M', 'd'),
(0x1D4EE, 'M', 'e'),
@@ -6514,11 +6611,6 @@ def _seg_61():
(0x1D51B, 'M', 'x'),
(0x1D51C, 'M', 'y'),
(0x1D51D, 'X'),
- ]
-
-def _seg_62():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1D51E, 'M', 'a'),
(0x1D51F, 'M', 'b'),
(0x1D520, 'M', 'c'),
@@ -6570,6 +6662,10 @@ def _seg_62():
(0x1D550, 'M', 'y'),
(0x1D551, 'X'),
(0x1D552, 'M', 'a'),
+ ]
+
+def _seg_64() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1D553, 'M', 'b'),
(0x1D554, 'M', 'c'),
(0x1D555, 'M', 'd'),
@@ -6619,11 +6715,6 @@ def _seg_62():
(0x1D581, 'M', 'v'),
(0x1D582, 'M', 'w'),
(0x1D583, 'M', 'x'),
- ]
-
-def _seg_63():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1D584, 'M', 'y'),
(0x1D585, 'M', 'z'),
(0x1D586, 'M', 'a'),
@@ -6675,6 +6766,10 @@ def _seg_63():
(0x1D5B4, 'M', 'u'),
(0x1D5B5, 'M', 'v'),
(0x1D5B6, 'M', 'w'),
+ ]
+
+def _seg_65() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1D5B7, 'M', 'x'),
(0x1D5B8, 'M', 'y'),
(0x1D5B9, 'M', 'z'),
@@ -6724,11 +6819,6 @@ def _seg_63():
(0x1D5E5, 'M', 'r'),
(0x1D5E6, 'M', 's'),
(0x1D5E7, 'M', 't'),
- ]
-
-def _seg_64():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1D5E8, 'M', 'u'),
(0x1D5E9, 'M', 'v'),
(0x1D5EA, 'M', 'w'),
@@ -6780,6 +6870,10 @@ def _seg_64():
(0x1D618, 'M', 'q'),
(0x1D619, 'M', 'r'),
(0x1D61A, 'M', 's'),
+ ]
+
+def _seg_66() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1D61B, 'M', 't'),
(0x1D61C, 'M', 'u'),
(0x1D61D, 'M', 'v'),
@@ -6829,11 +6923,6 @@ def _seg_64():
(0x1D649, 'M', 'n'),
(0x1D64A, 'M', 'o'),
(0x1D64B, 'M', 'p'),
- ]
-
-def _seg_65():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1D64C, 'M', 'q'),
(0x1D64D, 'M', 'r'),
(0x1D64E, 'M', 's'),
@@ -6885,6 +6974,10 @@ def _seg_65():
(0x1D67C, 'M', 'm'),
(0x1D67D, 'M', 'n'),
(0x1D67E, 'M', 'o'),
+ ]
+
+def _seg_67() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1D67F, 'M', 'p'),
(0x1D680, 'M', 'q'),
(0x1D681, 'M', 'r'),
@@ -6934,11 +7027,6 @@ def _seg_65():
(0x1D6AE, 'M', 'η'),
(0x1D6AF, 'M', 'θ'),
(0x1D6B0, 'M', 'ι'),
- ]
-
-def _seg_66():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1D6B1, 'M', 'κ'),
(0x1D6B2, 'M', 'λ'),
(0x1D6B3, 'M', 'μ'),
@@ -6990,6 +7078,10 @@ def _seg_66():
(0x1D6E2, 'M', 'α'),
(0x1D6E3, 'M', 'β'),
(0x1D6E4, 'M', 'γ'),
+ ]
+
+def _seg_68() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1D6E5, 'M', 'δ'),
(0x1D6E6, 'M', 'ε'),
(0x1D6E7, 'M', 'ζ'),
@@ -7039,11 +7131,6 @@ def _seg_66():
(0x1D714, 'M', 'ω'),
(0x1D715, 'M', '∂'),
(0x1D716, 'M', 'ε'),
- ]
-
-def _seg_67():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1D717, 'M', 'θ'),
(0x1D718, 'M', 'κ'),
(0x1D719, 'M', 'φ'),
@@ -7095,6 +7182,10 @@ def _seg_67():
(0x1D747, 'M', 'σ'),
(0x1D749, 'M', 'τ'),
(0x1D74A, 'M', 'υ'),
+ ]
+
+def _seg_69() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1D74B, 'M', 'φ'),
(0x1D74C, 'M', 'χ'),
(0x1D74D, 'M', 'ψ'),
@@ -7144,11 +7235,6 @@ def _seg_67():
(0x1D779, 'M', 'κ'),
(0x1D77A, 'M', 'λ'),
(0x1D77B, 'M', 'μ'),
- ]
-
-def _seg_68():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1D77C, 'M', 'ν'),
(0x1D77D, 'M', 'ξ'),
(0x1D77E, 'M', 'ο'),
@@ -7200,6 +7286,10 @@ def _seg_68():
(0x1D7AD, 'M', 'δ'),
(0x1D7AE, 'M', 'ε'),
(0x1D7AF, 'M', 'ζ'),
+ ]
+
+def _seg_70() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1D7B0, 'M', 'η'),
(0x1D7B1, 'M', 'θ'),
(0x1D7B2, 'M', 'ι'),
@@ -7249,11 +7339,6 @@ def _seg_68():
(0x1D7E1, 'M', '9'),
(0x1D7E2, 'M', '0'),
(0x1D7E3, 'M', '1'),
- ]
-
-def _seg_69():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1D7E4, 'M', '2'),
(0x1D7E5, 'M', '3'),
(0x1D7E6, 'M', '4'),
@@ -7288,6 +7373,10 @@ def _seg_69():
(0x1DAA0, 'X'),
(0x1DAA1, 'V'),
(0x1DAB0, 'X'),
+ (0x1DF00, 'V'),
+ (0x1DF1F, 'X'),
+ (0x1DF25, 'V'),
+ (0x1DF2B, 'X'),
(0x1E000, 'V'),
(0x1E007, 'X'),
(0x1E008, 'V'),
@@ -7298,6 +7387,75 @@ def _seg_69():
(0x1E025, 'X'),
(0x1E026, 'V'),
(0x1E02B, 'X'),
+ (0x1E030, 'M', 'а'),
+ (0x1E031, 'M', 'б'),
+ (0x1E032, 'M', 'в'),
+ ]
+
+def _seg_71() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1E033, 'M', 'г'),
+ (0x1E034, 'M', 'д'),
+ (0x1E035, 'M', 'е'),
+ (0x1E036, 'M', 'ж'),
+ (0x1E037, 'M', 'з'),
+ (0x1E038, 'M', 'и'),
+ (0x1E039, 'M', 'к'),
+ (0x1E03A, 'M', 'л'),
+ (0x1E03B, 'M', 'м'),
+ (0x1E03C, 'M', 'о'),
+ (0x1E03D, 'M', 'п'),
+ (0x1E03E, 'M', 'р'),
+ (0x1E03F, 'M', 'с'),
+ (0x1E040, 'M', 'т'),
+ (0x1E041, 'M', 'у'),
+ (0x1E042, 'M', 'ф'),
+ (0x1E043, 'M', 'х'),
+ (0x1E044, 'M', 'ц'),
+ (0x1E045, 'M', 'ч'),
+ (0x1E046, 'M', 'ш'),
+ (0x1E047, 'M', 'ы'),
+ (0x1E048, 'M', 'э'),
+ (0x1E049, 'M', 'ю'),
+ (0x1E04A, 'M', 'ꚉ'),
+ (0x1E04B, 'M', 'ә'),
+ (0x1E04C, 'M', 'і'),
+ (0x1E04D, 'M', 'ј'),
+ (0x1E04E, 'M', 'ө'),
+ (0x1E04F, 'M', 'ү'),
+ (0x1E050, 'M', 'ӏ'),
+ (0x1E051, 'M', 'а'),
+ (0x1E052, 'M', 'б'),
+ (0x1E053, 'M', 'в'),
+ (0x1E054, 'M', 'г'),
+ (0x1E055, 'M', 'д'),
+ (0x1E056, 'M', 'е'),
+ (0x1E057, 'M', 'ж'),
+ (0x1E058, 'M', 'з'),
+ (0x1E059, 'M', 'и'),
+ (0x1E05A, 'M', 'к'),
+ (0x1E05B, 'M', 'л'),
+ (0x1E05C, 'M', 'о'),
+ (0x1E05D, 'M', 'п'),
+ (0x1E05E, 'M', 'с'),
+ (0x1E05F, 'M', 'у'),
+ (0x1E060, 'M', 'ф'),
+ (0x1E061, 'M', 'х'),
+ (0x1E062, 'M', 'ц'),
+ (0x1E063, 'M', 'ч'),
+ (0x1E064, 'M', 'ш'),
+ (0x1E065, 'M', 'ъ'),
+ (0x1E066, 'M', 'ы'),
+ (0x1E067, 'M', 'ґ'),
+ (0x1E068, 'M', 'і'),
+ (0x1E069, 'M', 'ѕ'),
+ (0x1E06A, 'M', 'џ'),
+ (0x1E06B, 'M', 'ҫ'),
+ (0x1E06C, 'M', 'ꙑ'),
+ (0x1E06D, 'M', 'ұ'),
+ (0x1E06E, 'X'),
+ (0x1E08F, 'V'),
+ (0x1E090, 'X'),
(0x1E100, 'V'),
(0x1E12D, 'X'),
(0x1E130, 'V'),
@@ -7306,10 +7464,22 @@ def _seg_69():
(0x1E14A, 'X'),
(0x1E14E, 'V'),
(0x1E150, 'X'),
+ (0x1E290, 'V'),
+ (0x1E2AF, 'X'),
(0x1E2C0, 'V'),
(0x1E2FA, 'X'),
(0x1E2FF, 'V'),
(0x1E300, 'X'),
+ (0x1E4D0, 'V'),
+ (0x1E4FA, 'X'),
+ (0x1E7E0, 'V'),
+ (0x1E7E7, 'X'),
+ (0x1E7E8, 'V'),
+ (0x1E7EC, 'X'),
+ (0x1E7ED, 'V'),
+ (0x1E7EF, 'X'),
+ (0x1E7F0, 'V'),
+ (0x1E7FF, 'X'),
(0x1E800, 'V'),
(0x1E8C5, 'X'),
(0x1E8C7, 'V'),
@@ -7324,6 +7494,10 @@ def _seg_69():
(0x1E907, 'M', '𞤩'),
(0x1E908, 'M', '𞤪'),
(0x1E909, 'M', '𞤫'),
+ ]
+
+def _seg_72() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1E90A, 'M', '𞤬'),
(0x1E90B, 'M', '𞤭'),
(0x1E90C, 'M', '𞤮'),
@@ -7354,11 +7528,6 @@ def _seg_69():
(0x1E95A, 'X'),
(0x1E95E, 'V'),
(0x1E960, 'X'),
- ]
-
-def _seg_70():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1EC71, 'V'),
(0x1ECB5, 'X'),
(0x1ED01, 'V'),
@@ -7429,6 +7598,10 @@ def _seg_70():
(0x1EE48, 'X'),
(0x1EE49, 'M', 'ي'),
(0x1EE4A, 'X'),
+ ]
+
+def _seg_73() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1EE4B, 'M', 'ل'),
(0x1EE4C, 'X'),
(0x1EE4D, 'M', 'ن'),
@@ -7459,11 +7632,6 @@ def _seg_70():
(0x1EE68, 'M', 'ط'),
(0x1EE69, 'M', 'ي'),
(0x1EE6A, 'M', 'ك'),
- ]
-
-def _seg_71():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1EE6B, 'X'),
(0x1EE6C, 'M', 'م'),
(0x1EE6D, 'M', 'ن'),
@@ -7534,6 +7702,10 @@ def _seg_71():
(0x1EEB2, 'M', 'ق'),
(0x1EEB3, 'M', 'ر'),
(0x1EEB4, 'M', 'ش'),
+ ]
+
+def _seg_74() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1EEB5, 'M', 'ت'),
(0x1EEB6, 'M', 'ث'),
(0x1EEB7, 'M', 'خ'),
@@ -7564,11 +7736,6 @@ def _seg_71():
(0x1F106, '3', '5,'),
(0x1F107, '3', '6,'),
(0x1F108, '3', '7,'),
- ]
-
-def _seg_72():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1F109, '3', '8,'),
(0x1F10A, '3', '9,'),
(0x1F10B, 'V'),
@@ -7639,6 +7806,10 @@ def _seg_72():
(0x1F150, 'V'),
(0x1F16A, 'M', 'mc'),
(0x1F16B, 'M', 'md'),
+ ]
+
+def _seg_75() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1F16C, 'M', 'mr'),
(0x1F16D, 'V'),
(0x1F190, 'M', 'dj'),
@@ -7669,11 +7840,6 @@ def _seg_72():
(0x1F221, 'M', '終'),
(0x1F222, 'M', '生'),
(0x1F223, 'M', '販'),
- ]
-
-def _seg_73():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1F224, 'M', '声'),
(0x1F225, 'M', '吹'),
(0x1F226, 'M', '演'),
@@ -7716,16 +7882,18 @@ def _seg_73():
(0x1F266, 'X'),
(0x1F300, 'V'),
(0x1F6D8, 'X'),
- (0x1F6E0, 'V'),
+ (0x1F6DC, 'V'),
(0x1F6ED, 'X'),
(0x1F6F0, 'V'),
(0x1F6FD, 'X'),
(0x1F700, 'V'),
- (0x1F774, 'X'),
- (0x1F780, 'V'),
- (0x1F7D9, 'X'),
+ (0x1F777, 'X'),
+ (0x1F77B, 'V'),
+ (0x1F7DA, 'X'),
(0x1F7E0, 'V'),
(0x1F7EC, 'X'),
+ (0x1F7F0, 'V'),
+ (0x1F7F1, 'X'),
(0x1F800, 'V'),
(0x1F80C, 'X'),
(0x1F810, 'V'),
@@ -7739,27 +7907,27 @@ def _seg_73():
(0x1F8B0, 'V'),
(0x1F8B2, 'X'),
(0x1F900, 'V'),
- (0x1F979, 'X'),
- (0x1F97A, 'V'),
- (0x1F9CC, 'X'),
- (0x1F9CD, 'V'),
(0x1FA54, 'X'),
(0x1FA60, 'V'),
(0x1FA6E, 'X'),
+ ]
+
+def _seg_76() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x1FA70, 'V'),
- (0x1FA75, 'X'),
- (0x1FA78, 'V'),
- (0x1FA7B, 'X'),
+ (0x1FA7D, 'X'),
(0x1FA80, 'V'),
- (0x1FA87, 'X'),
+ (0x1FA89, 'X'),
(0x1FA90, 'V'),
- (0x1FAA9, 'X'),
- (0x1FAB0, 'V'),
- (0x1FAB7, 'X'),
- (0x1FAC0, 'V'),
- (0x1FAC3, 'X'),
- (0x1FAD0, 'V'),
- (0x1FAD7, 'X'),
+ (0x1FABE, 'X'),
+ (0x1FABF, 'V'),
+ (0x1FAC6, 'X'),
+ (0x1FACE, 'V'),
+ (0x1FADC, 'X'),
+ (0x1FAE0, 'V'),
+ (0x1FAE9, 'X'),
+ (0x1FAF0, 'V'),
+ (0x1FAF9, 'X'),
(0x1FB00, 'V'),
(0x1FB93, 'X'),
(0x1FB94, 'V'),
@@ -7774,16 +7942,11 @@ def _seg_73():
(0x1FBF7, 'M', '7'),
(0x1FBF8, 'M', '8'),
(0x1FBF9, 'M', '9'),
- ]
-
-def _seg_74():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x1FBFA, 'X'),
(0x20000, 'V'),
- (0x2A6DE, 'X'),
+ (0x2A6E0, 'X'),
(0x2A700, 'V'),
- (0x2B735, 'X'),
+ (0x2B73A, 'X'),
(0x2B740, 'V'),
(0x2B81E, 'X'),
(0x2B820, 'V'),
@@ -7851,6 +8014,10 @@ def _seg_74():
(0x2F83C, 'M', '咞'),
(0x2F83D, 'M', '吸'),
(0x2F83E, 'M', '呈'),
+ ]
+
+def _seg_77() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x2F83F, 'M', '周'),
(0x2F840, 'M', '咢'),
(0x2F841, 'M', '哶'),
@@ -7879,11 +8046,6 @@ def _seg_74():
(0x2F859, 'M', '𡓤'),
(0x2F85A, 'M', '売'),
(0x2F85B, 'M', '壷'),
- ]
-
-def _seg_75():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x2F85C, 'M', '夆'),
(0x2F85D, 'M', '多'),
(0x2F85E, 'M', '夢'),
@@ -7956,6 +8118,10 @@ def _seg_75():
(0x2F8A4, 'M', '𢛔'),
(0x2F8A5, 'M', '惇'),
(0x2F8A6, 'M', '慈'),
+ ]
+
+def _seg_78() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x2F8A7, 'M', '慌'),
(0x2F8A8, 'M', '慎'),
(0x2F8A9, 'M', '慌'),
@@ -7984,11 +8150,6 @@ def _seg_75():
(0x2F8C0, 'M', '揅'),
(0x2F8C1, 'M', '掩'),
(0x2F8C2, 'M', '㨮'),
- ]
-
-def _seg_76():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x2F8C3, 'M', '摩'),
(0x2F8C4, 'M', '摾'),
(0x2F8C5, 'M', '撝'),
@@ -8061,6 +8222,10 @@ def _seg_76():
(0x2F908, 'M', '港'),
(0x2F909, 'M', '湮'),
(0x2F90A, 'M', '㴳'),
+ ]
+
+def _seg_79() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x2F90B, 'M', '滋'),
(0x2F90C, 'M', '滇'),
(0x2F90D, 'M', '𣻑'),
@@ -8089,11 +8254,6 @@ def _seg_76():
(0x2F924, 'M', '犀'),
(0x2F925, 'M', '犕'),
(0x2F926, 'M', '𤜵'),
- ]
-
-def _seg_77():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x2F927, 'M', '𤠔'),
(0x2F928, 'M', '獺'),
(0x2F929, 'M', '王'),
@@ -8166,6 +8326,10 @@ def _seg_77():
(0x2F96F, 'M', '縂'),
(0x2F970, 'M', '繅'),
(0x2F971, 'M', '䌴'),
+ ]
+
+def _seg_80() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x2F972, 'M', '𦈨'),
(0x2F973, 'M', '𦉇'),
(0x2F974, 'M', '䍙'),
@@ -8194,11 +8358,6 @@ def _seg_77():
(0x2F98B, 'M', '舁'),
(0x2F98C, 'M', '舄'),
(0x2F98D, 'M', '辞'),
- ]
-
-def _seg_78():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x2F98E, 'M', '䑫'),
(0x2F98F, 'M', '芑'),
(0x2F990, 'M', '芋'),
@@ -8271,6 +8430,10 @@ def _seg_78():
(0x2F9D3, 'M', '𧲨'),
(0x2F9D4, 'M', '貫'),
(0x2F9D5, 'M', '賁'),
+ ]
+
+def _seg_81() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
(0x2F9D6, 'M', '贛'),
(0x2F9D7, 'M', '起'),
(0x2F9D8, 'M', '𧼯'),
@@ -8299,11 +8462,6 @@ def _seg_78():
(0x2F9EF, 'M', '䦕'),
(0x2F9F0, 'M', '閷'),
(0x2F9F1, 'M', '𨵷'),
- ]
-
-def _seg_79():
- # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]]
- return [
(0x2F9F2, 'M', '䧦'),
(0x2F9F3, 'M', '雃'),
(0x2F9F4, 'M', '嶲'),
@@ -8350,6 +8508,8 @@ def _seg_79():
(0x2FA1E, 'X'),
(0x30000, 'V'),
(0x3134B, 'X'),
+ (0x31350, 'V'),
+ (0x323B0, 'X'),
(0xE0100, 'I'),
(0xE01F0, 'X'),
]
@@ -8435,4 +8595,6 @@ uts46data = tuple(
+ _seg_77()
+ _seg_78()
+ _seg_79()
+ + _seg_80()
+ + _seg_81()
) # type: Tuple[Union[Tuple[int, str], Tuple[int, str, str]], ...]
diff --git a/src/pip/_vendor/msgpack/__init__.py b/src/pip/_vendor/msgpack/__init__.py
index d6705e22b..507102189 100644
--- a/src/pip/_vendor/msgpack/__init__.py
+++ b/src/pip/_vendor/msgpack/__init__.py
@@ -1,5 +1,4 @@
# coding: utf-8
-from ._version import version
from .exceptions import *
from .ext import ExtType, Timestamp
@@ -7,6 +6,10 @@ import os
import sys
+version = (1, 0, 4)
+__version__ = "1.0.4"
+
+
if os.environ.get("MSGPACK_PUREPYTHON") or sys.version_info[0] == 2:
from .fallback import Packer, unpackb, Unpacker
else:
diff --git a/src/pip/_vendor/msgpack/_version.py b/src/pip/_vendor/msgpack/_version.py
deleted file mode 100644
index 1c83c8ed3..000000000
--- a/src/pip/_vendor/msgpack/_version.py
+++ /dev/null
@@ -1 +0,0 @@
-version = (1, 0, 2)
diff --git a/src/pip/_vendor/msgpack/ext.py b/src/pip/_vendor/msgpack/ext.py
index 4eb9dd65a..25544c555 100644
--- a/src/pip/_vendor/msgpack/ext.py
+++ b/src/pip/_vendor/msgpack/ext.py
@@ -59,7 +59,7 @@ class Timestamp(object):
raise TypeError("seconds must be an interger")
if not isinstance(nanoseconds, int_types):
raise TypeError("nanoseconds must be an integer")
- if not (0 <= nanoseconds < 10 ** 9):
+ if not (0 <= nanoseconds < 10**9):
raise ValueError(
"nanoseconds must be a non-negative integer less than 999999999."
)
@@ -143,7 +143,7 @@ class Timestamp(object):
:type unix_float: int or float.
"""
seconds = int(unix_sec // 1)
- nanoseconds = int((unix_sec % 1) * 10 ** 9)
+ nanoseconds = int((unix_sec % 1) * 10**9)
return Timestamp(seconds, nanoseconds)
def to_unix(self):
@@ -161,7 +161,7 @@ class Timestamp(object):
:param int unix_ns: Posix timestamp in nanoseconds.
:rtype: Timestamp
"""
- return Timestamp(*divmod(unix_ns, 10 ** 9))
+ return Timestamp(*divmod(unix_ns, 10**9))
def to_unix_nano(self):
"""Get the timestamp as a unixtime in nanoseconds.
@@ -169,7 +169,7 @@ class Timestamp(object):
:returns: posix timestamp in nanoseconds
:rtype: int
"""
- return self.seconds * 10 ** 9 + self.nanoseconds
+ return self.seconds * 10**9 + self.nanoseconds
def to_datetime(self):
"""Get the timestamp as a UTC datetime.
diff --git a/src/pip/_vendor/msgpack/fallback.py b/src/pip/_vendor/msgpack/fallback.py
index 0bfa94eac..f560c7b55 100644
--- a/src/pip/_vendor/msgpack/fallback.py
+++ b/src/pip/_vendor/msgpack/fallback.py
@@ -1,5 +1,4 @@
"""Fallback pure Python implementation of msgpack"""
-
from datetime import datetime as _DateTime
import sys
import struct
@@ -12,7 +11,6 @@ if PY2:
def dict_iteritems(d):
return d.iteritems()
-
else:
int_types = int
unicode = str
@@ -33,7 +31,6 @@ if sys.version_info < (3, 5):
and e.args[0].startswith("maximum recursion depth exceeded")
)
-
else:
def _is_recursionerror(e):
@@ -69,7 +66,6 @@ if hasattr(sys, "pypy_version_info"):
def getvalue(self):
return self.builder.build()
-
else:
USING_STRINGBUILDER = False
from io import BytesIO as StringIO
@@ -144,10 +140,41 @@ if sys.version_info < (2, 7, 6):
"""Explicit type cast for legacy struct.unpack_from"""
return struct.unpack_from(f, bytes(b), o)
-
else:
_unpack_from = struct.unpack_from
+_NO_FORMAT_USED = ""
+_MSGPACK_HEADERS = {
+ 0xC4: (1, _NO_FORMAT_USED, TYPE_BIN),
+ 0xC5: (2, ">H", TYPE_BIN),
+ 0xC6: (4, ">I", TYPE_BIN),
+ 0xC7: (2, "Bb", TYPE_EXT),
+ 0xC8: (3, ">Hb", TYPE_EXT),
+ 0xC9: (5, ">Ib", TYPE_EXT),
+ 0xCA: (4, ">f"),
+ 0xCB: (8, ">d"),
+ 0xCC: (1, _NO_FORMAT_USED),
+ 0xCD: (2, ">H"),
+ 0xCE: (4, ">I"),
+ 0xCF: (8, ">Q"),
+ 0xD0: (1, "b"),
+ 0xD1: (2, ">h"),
+ 0xD2: (4, ">i"),
+ 0xD3: (8, ">q"),
+ 0xD4: (1, "b1s", TYPE_EXT),
+ 0xD5: (2, "b2s", TYPE_EXT),
+ 0xD6: (4, "b4s", TYPE_EXT),
+ 0xD7: (8, "b8s", TYPE_EXT),
+ 0xD8: (16, "b16s", TYPE_EXT),
+ 0xD9: (1, _NO_FORMAT_USED, TYPE_RAW),
+ 0xDA: (2, ">H", TYPE_RAW),
+ 0xDB: (4, ">I", TYPE_RAW),
+ 0xDC: (2, ">H", TYPE_ARRAY),
+ 0xDD: (4, ">I", TYPE_ARRAY),
+ 0xDE: (2, ">H", TYPE_MAP),
+ 0xDF: (4, ">I", TYPE_MAP),
+}
+
class Unpacker(object):
"""Streaming unpacker.
@@ -229,7 +256,7 @@ class Unpacker(object):
Example of streaming deserialize from socket::
- unpacker = Unpacker(max_buffer_size)
+ unpacker = Unpacker()
while True:
buf = sock.recv(1024**2)
if not buf:
@@ -291,7 +318,7 @@ class Unpacker(object):
self._buf_checkpoint = 0
if not max_buffer_size:
- max_buffer_size = 2 ** 31 - 1
+ max_buffer_size = 2**31 - 1
if max_str_len == -1:
max_str_len = max_buffer_size
if max_bin_len == -1:
@@ -354,7 +381,7 @@ class Unpacker(object):
self._buffer.extend(view)
def _consume(self):
- """ Gets rid of the used parts of the buffer. """
+ """Gets rid of the used parts of the buffer."""
self._stream_offset += self._buff_i - self._buf_checkpoint
self._buf_checkpoint = self._buff_i
@@ -396,6 +423,8 @@ class Unpacker(object):
# Read from file
remain_bytes = -remain_bytes
+ if remain_bytes + len(self._buffer) > self._max_buffer_size:
+ raise BufferFull
while remain_bytes > 0:
to_read_bytes = max(self._read_size, remain_bytes)
read_data = self.file_like.read(to_read_bytes)
@@ -409,7 +438,7 @@ class Unpacker(object):
self._buff_i = 0 # rollback
raise OutOfData
- def _read_header(self, execute=EX_CONSTRUCT):
+ def _read_header(self):
typ = TYPE_IMMEDIATE
n = 0
obj = None
@@ -424,205 +453,95 @@ class Unpacker(object):
n = b & 0b00011111
typ = TYPE_RAW
if n > self._max_str_len:
- raise ValueError("%s exceeds max_str_len(%s)", n, self._max_str_len)
+ raise ValueError("%s exceeds max_str_len(%s)" % (n, self._max_str_len))
obj = self._read(n)
elif b & 0b11110000 == 0b10010000:
n = b & 0b00001111
typ = TYPE_ARRAY
if n > self._max_array_len:
- raise ValueError("%s exceeds max_array_len(%s)", n, self._max_array_len)
+ raise ValueError(
+ "%s exceeds max_array_len(%s)" % (n, self._max_array_len)
+ )
elif b & 0b11110000 == 0b10000000:
n = b & 0b00001111
typ = TYPE_MAP
if n > self._max_map_len:
- raise ValueError("%s exceeds max_map_len(%s)", n, self._max_map_len)
+ raise ValueError("%s exceeds max_map_len(%s)" % (n, self._max_map_len))
elif b == 0xC0:
obj = None
elif b == 0xC2:
obj = False
elif b == 0xC3:
obj = True
- elif b == 0xC4:
- typ = TYPE_BIN
- self._reserve(1)
- n = self._buffer[self._buff_i]
- self._buff_i += 1
- if n > self._max_bin_len:
- raise ValueError("%s exceeds max_bin_len(%s)" % (n, self._max_bin_len))
- obj = self._read(n)
- elif b == 0xC5:
- typ = TYPE_BIN
- self._reserve(2)
- n = _unpack_from(">H", self._buffer, self._buff_i)[0]
- self._buff_i += 2
- if n > self._max_bin_len:
- raise ValueError("%s exceeds max_bin_len(%s)" % (n, self._max_bin_len))
- obj = self._read(n)
- elif b == 0xC6:
- typ = TYPE_BIN
- self._reserve(4)
- n = _unpack_from(">I", self._buffer, self._buff_i)[0]
- self._buff_i += 4
+ elif 0xC4 <= b <= 0xC6:
+ size, fmt, typ = _MSGPACK_HEADERS[b]
+ self._reserve(size)
+ if len(fmt) > 0:
+ n = _unpack_from(fmt, self._buffer, self._buff_i)[0]
+ else:
+ n = self._buffer[self._buff_i]
+ self._buff_i += size
if n > self._max_bin_len:
raise ValueError("%s exceeds max_bin_len(%s)" % (n, self._max_bin_len))
obj = self._read(n)
- elif b == 0xC7: # ext 8
- typ = TYPE_EXT
- self._reserve(2)
- L, n = _unpack_from("Bb", self._buffer, self._buff_i)
- self._buff_i += 2
+ elif 0xC7 <= b <= 0xC9:
+ size, fmt, typ = _MSGPACK_HEADERS[b]
+ self._reserve(size)
+ L, n = _unpack_from(fmt, self._buffer, self._buff_i)
+ self._buff_i += size
if L > self._max_ext_len:
raise ValueError("%s exceeds max_ext_len(%s)" % (L, self._max_ext_len))
obj = self._read(L)
- elif b == 0xC8: # ext 16
- typ = TYPE_EXT
- self._reserve(3)
- L, n = _unpack_from(">Hb", self._buffer, self._buff_i)
- self._buff_i += 3
- if L > self._max_ext_len:
- raise ValueError("%s exceeds max_ext_len(%s)" % (L, self._max_ext_len))
- obj = self._read(L)
- elif b == 0xC9: # ext 32
- typ = TYPE_EXT
- self._reserve(5)
- L, n = _unpack_from(">Ib", self._buffer, self._buff_i)
- self._buff_i += 5
- if L > self._max_ext_len:
- raise ValueError("%s exceeds max_ext_len(%s)" % (L, self._max_ext_len))
- obj = self._read(L)
- elif b == 0xCA:
- self._reserve(4)
- obj = _unpack_from(">f", self._buffer, self._buff_i)[0]
- self._buff_i += 4
- elif b == 0xCB:
- self._reserve(8)
- obj = _unpack_from(">d", self._buffer, self._buff_i)[0]
- self._buff_i += 8
- elif b == 0xCC:
- self._reserve(1)
- obj = self._buffer[self._buff_i]
- self._buff_i += 1
- elif b == 0xCD:
- self._reserve(2)
- obj = _unpack_from(">H", self._buffer, self._buff_i)[0]
- self._buff_i += 2
- elif b == 0xCE:
- self._reserve(4)
- obj = _unpack_from(">I", self._buffer, self._buff_i)[0]
- self._buff_i += 4
- elif b == 0xCF:
- self._reserve(8)
- obj = _unpack_from(">Q", self._buffer, self._buff_i)[0]
- self._buff_i += 8
- elif b == 0xD0:
- self._reserve(1)
- obj = _unpack_from("b", self._buffer, self._buff_i)[0]
- self._buff_i += 1
- elif b == 0xD1:
- self._reserve(2)
- obj = _unpack_from(">h", self._buffer, self._buff_i)[0]
- self._buff_i += 2
- elif b == 0xD2:
- self._reserve(4)
- obj = _unpack_from(">i", self._buffer, self._buff_i)[0]
- self._buff_i += 4
- elif b == 0xD3:
- self._reserve(8)
- obj = _unpack_from(">q", self._buffer, self._buff_i)[0]
- self._buff_i += 8
- elif b == 0xD4: # fixext 1
- typ = TYPE_EXT
- if self._max_ext_len < 1:
- raise ValueError("%s exceeds max_ext_len(%s)" % (1, self._max_ext_len))
- self._reserve(2)
- n, obj = _unpack_from("b1s", self._buffer, self._buff_i)
- self._buff_i += 2
- elif b == 0xD5: # fixext 2
- typ = TYPE_EXT
- if self._max_ext_len < 2:
- raise ValueError("%s exceeds max_ext_len(%s)" % (2, self._max_ext_len))
- self._reserve(3)
- n, obj = _unpack_from("b2s", self._buffer, self._buff_i)
- self._buff_i += 3
- elif b == 0xD6: # fixext 4
- typ = TYPE_EXT
- if self._max_ext_len < 4:
- raise ValueError("%s exceeds max_ext_len(%s)" % (4, self._max_ext_len))
- self._reserve(5)
- n, obj = _unpack_from("b4s", self._buffer, self._buff_i)
- self._buff_i += 5
- elif b == 0xD7: # fixext 8
- typ = TYPE_EXT
- if self._max_ext_len < 8:
- raise ValueError("%s exceeds max_ext_len(%s)" % (8, self._max_ext_len))
- self._reserve(9)
- n, obj = _unpack_from("b8s", self._buffer, self._buff_i)
- self._buff_i += 9
- elif b == 0xD8: # fixext 16
- typ = TYPE_EXT
- if self._max_ext_len < 16:
- raise ValueError("%s exceeds max_ext_len(%s)" % (16, self._max_ext_len))
- self._reserve(17)
- n, obj = _unpack_from("b16s", self._buffer, self._buff_i)
- self._buff_i += 17
- elif b == 0xD9:
- typ = TYPE_RAW
- self._reserve(1)
- n = self._buffer[self._buff_i]
- self._buff_i += 1
- if n > self._max_str_len:
- raise ValueError("%s exceeds max_str_len(%s)", n, self._max_str_len)
- obj = self._read(n)
- elif b == 0xDA:
- typ = TYPE_RAW
- self._reserve(2)
- (n,) = _unpack_from(">H", self._buffer, self._buff_i)
- self._buff_i += 2
- if n > self._max_str_len:
- raise ValueError("%s exceeds max_str_len(%s)", n, self._max_str_len)
- obj = self._read(n)
- elif b == 0xDB:
- typ = TYPE_RAW
- self._reserve(4)
- (n,) = _unpack_from(">I", self._buffer, self._buff_i)
- self._buff_i += 4
+ elif 0xCA <= b <= 0xD3:
+ size, fmt = _MSGPACK_HEADERS[b]
+ self._reserve(size)
+ if len(fmt) > 0:
+ obj = _unpack_from(fmt, self._buffer, self._buff_i)[0]
+ else:
+ obj = self._buffer[self._buff_i]
+ self._buff_i += size
+ elif 0xD4 <= b <= 0xD8:
+ size, fmt, typ = _MSGPACK_HEADERS[b]
+ if self._max_ext_len < size:
+ raise ValueError(
+ "%s exceeds max_ext_len(%s)" % (size, self._max_ext_len)
+ )
+ self._reserve(size + 1)
+ n, obj = _unpack_from(fmt, self._buffer, self._buff_i)
+ self._buff_i += size + 1
+ elif 0xD9 <= b <= 0xDB:
+ size, fmt, typ = _MSGPACK_HEADERS[b]
+ self._reserve(size)
+ if len(fmt) > 0:
+ (n,) = _unpack_from(fmt, self._buffer, self._buff_i)
+ else:
+ n = self._buffer[self._buff_i]
+ self._buff_i += size
if n > self._max_str_len:
- raise ValueError("%s exceeds max_str_len(%s)", n, self._max_str_len)
+ raise ValueError("%s exceeds max_str_len(%s)" % (n, self._max_str_len))
obj = self._read(n)
- elif b == 0xDC:
- typ = TYPE_ARRAY
- self._reserve(2)
- (n,) = _unpack_from(">H", self._buffer, self._buff_i)
- self._buff_i += 2
+ elif 0xDC <= b <= 0xDD:
+ size, fmt, typ = _MSGPACK_HEADERS[b]
+ self._reserve(size)
+ (n,) = _unpack_from(fmt, self._buffer, self._buff_i)
+ self._buff_i += size
if n > self._max_array_len:
- raise ValueError("%s exceeds max_array_len(%s)", n, self._max_array_len)
- elif b == 0xDD:
- typ = TYPE_ARRAY
- self._reserve(4)
- (n,) = _unpack_from(">I", self._buffer, self._buff_i)
- self._buff_i += 4
- if n > self._max_array_len:
- raise ValueError("%s exceeds max_array_len(%s)", n, self._max_array_len)
- elif b == 0xDE:
- self._reserve(2)
- (n,) = _unpack_from(">H", self._buffer, self._buff_i)
- self._buff_i += 2
- if n > self._max_map_len:
- raise ValueError("%s exceeds max_map_len(%s)", n, self._max_map_len)
- typ = TYPE_MAP
- elif b == 0xDF:
- self._reserve(4)
- (n,) = _unpack_from(">I", self._buffer, self._buff_i)
- self._buff_i += 4
+ raise ValueError(
+ "%s exceeds max_array_len(%s)" % (n, self._max_array_len)
+ )
+ elif 0xDE <= b <= 0xDF:
+ size, fmt, typ = _MSGPACK_HEADERS[b]
+ self._reserve(size)
+ (n,) = _unpack_from(fmt, self._buffer, self._buff_i)
+ self._buff_i += size
if n > self._max_map_len:
- raise ValueError("%s exceeds max_map_len(%s)", n, self._max_map_len)
- typ = TYPE_MAP
+ raise ValueError("%s exceeds max_map_len(%s)" % (n, self._max_map_len))
else:
raise FormatError("Unknown header: 0x%x" % b)
return typ, n, obj
def _unpack(self, execute=EX_CONSTRUCT):
- typ, n, obj = self._read_header(execute)
+ typ, n, obj = self._read_header()
if execute == EX_READ_ARRAY_HEADER:
if typ != TYPE_ARRAY:
@@ -883,20 +802,20 @@ class Packer(object):
raise OverflowError("Integer value out of range")
if check(obj, (bytes, bytearray)):
n = len(obj)
- if n >= 2 ** 32:
+ if n >= 2**32:
raise ValueError("%s is too large" % type(obj).__name__)
self._pack_bin_header(n)
return self._buffer.write(obj)
if check(obj, unicode):
obj = obj.encode("utf-8", self._unicode_errors)
n = len(obj)
- if n >= 2 ** 32:
+ if n >= 2**32:
raise ValueError("String is too large")
self._pack_raw_header(n)
return self._buffer.write(obj)
if check(obj, memoryview):
n = len(obj) * obj.itemsize
- if n >= 2 ** 32:
+ if n >= 2**32:
raise ValueError("Memoryview is too large")
self._pack_bin_header(n)
return self._buffer.write(obj)
@@ -953,6 +872,10 @@ class Packer(object):
obj = self._default(obj)
default_used = 1
continue
+
+ if self._datetime and check(obj, _DateTime):
+ raise ValueError("Cannot serialize %r where tzinfo=None" % (obj,))
+
raise TypeError("Cannot serialize %r" % (obj,))
def pack(self, obj):
@@ -974,7 +897,7 @@ class Packer(object):
return ret
def pack_array_header(self, n):
- if n >= 2 ** 32:
+ if n >= 2**32:
raise ValueError
self._pack_array_header(n)
if self._autoreset:
@@ -983,7 +906,7 @@ class Packer(object):
return ret
def pack_map_header(self, n):
- if n >= 2 ** 32:
+ if n >= 2**32:
raise ValueError
self._pack_map_header(n)
if self._autoreset:
diff --git a/src/pip/_vendor/packaging/__about__.py b/src/pip/_vendor/packaging/__about__.py
index e70d692c8..3551bc2d2 100644
--- a/src/pip/_vendor/packaging/__about__.py
+++ b/src/pip/_vendor/packaging/__about__.py
@@ -17,7 +17,7 @@ __title__ = "packaging"
__summary__ = "Core utilities for Python packages"
__uri__ = "https://github.com/pypa/packaging"
-__version__ = "21.0"
+__version__ = "21.3"
__author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io"
diff --git a/src/pip/_vendor/packaging/_musllinux.py b/src/pip/_vendor/packaging/_musllinux.py
index 85450fafa..8ac3059ba 100644
--- a/src/pip/_vendor/packaging/_musllinux.py
+++ b/src/pip/_vendor/packaging/_musllinux.py
@@ -98,7 +98,7 @@ def _get_musl_version(executable: str) -> Optional[_MuslVersion]:
with contextlib.ExitStack() as stack:
try:
f = stack.enter_context(open(executable, "rb"))
- except IOError:
+ except OSError:
return None
ld = _parse_ld_musl_from_elf(f)
if not ld:
diff --git a/src/pip/_vendor/packaging/_structures.py b/src/pip/_vendor/packaging/_structures.py
index 951549753..90a6465f9 100644
--- a/src/pip/_vendor/packaging/_structures.py
+++ b/src/pip/_vendor/packaging/_structures.py
@@ -19,9 +19,6 @@ class InfinityType:
def __eq__(self, other: object) -> bool:
return isinstance(other, self.__class__)
- def __ne__(self, other: object) -> bool:
- return not isinstance(other, self.__class__)
-
def __gt__(self, other: object) -> bool:
return True
@@ -51,9 +48,6 @@ class NegativeInfinityType:
def __eq__(self, other: object) -> bool:
return isinstance(other, self.__class__)
- def __ne__(self, other: object) -> bool:
- return not isinstance(other, self.__class__)
-
def __gt__(self, other: object) -> bool:
return False
diff --git a/src/pip/_vendor/packaging/specifiers.py b/src/pip/_vendor/packaging/specifiers.py
index ce66bd4ad..0e218a6f9 100644
--- a/src/pip/_vendor/packaging/specifiers.py
+++ b/src/pip/_vendor/packaging/specifiers.py
@@ -57,13 +57,6 @@ class BaseSpecifier(metaclass=abc.ABCMeta):
objects are equal.
"""
- @abc.abstractmethod
- def __ne__(self, other: object) -> bool:
- """
- Returns a boolean representing whether or not the two Specifier like
- objects are not equal.
- """
-
@abc.abstractproperty
def prereleases(self) -> Optional[bool]:
"""
@@ -119,7 +112,7 @@ class _IndividualSpecifier(BaseSpecifier):
else ""
)
- return "<{}({!r}{})>".format(self.__class__.__name__, str(self), pre)
+ return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
def __str__(self) -> str:
return "{}{}".format(*self._spec)
@@ -142,17 +135,6 @@ class _IndividualSpecifier(BaseSpecifier):
return self._canonical_spec == other._canonical_spec
- def __ne__(self, other: object) -> bool:
- if isinstance(other, str):
- try:
- other = self.__class__(str(other))
- except InvalidSpecifier:
- return NotImplemented
- elif not isinstance(other, self.__class__):
- return NotImplemented
-
- return self._spec != other._spec
-
def _get_operator(self, op: str) -> CallableOperator:
operator_callable: CallableOperator = getattr(
self, f"_compare_{self._operators[op]}"
@@ -667,7 +649,7 @@ class SpecifierSet(BaseSpecifier):
else ""
)
- return "<SpecifierSet({!r}{})>".format(str(self), pre)
+ return f"<SpecifierSet({str(self)!r}{pre})>"
def __str__(self) -> str:
return ",".join(sorted(str(s) for s in self._specs))
@@ -706,14 +688,6 @@ class SpecifierSet(BaseSpecifier):
return self._specs == other._specs
- def __ne__(self, other: object) -> bool:
- if isinstance(other, (str, _IndividualSpecifier)):
- other = SpecifierSet(str(other))
- elif not isinstance(other, SpecifierSet):
- return NotImplemented
-
- return self._specs != other._specs
-
def __len__(self) -> int:
return len(self._specs)
diff --git a/src/pip/_vendor/packaging/tags.py b/src/pip/_vendor/packaging/tags.py
index 82a47cdae..9a3d25a71 100644
--- a/src/pip/_vendor/packaging/tags.py
+++ b/src/pip/_vendor/packaging/tags.py
@@ -90,7 +90,7 @@ class Tag:
return f"{self._interpreter}-{self._abi}-{self._platform}"
def __repr__(self) -> str:
- return "<{self} @ {self_id}>".format(self=self, self_id=id(self))
+ return f"<{self} @ {id(self)}>"
def parse_tag(tag: str) -> FrozenSet[Tag]:
@@ -192,7 +192,7 @@ def cpython_tags(
if not python_version:
python_version = sys.version_info[:2]
- interpreter = "cp{}".format(_version_nodot(python_version[:2]))
+ interpreter = f"cp{_version_nodot(python_version[:2])}"
if abis is None:
if len(python_version) > 1:
@@ -207,7 +207,7 @@ def cpython_tags(
except ValueError:
pass
- platforms = list(platforms or _platform_tags())
+ platforms = list(platforms or platform_tags())
for abi in abis:
for platform_ in platforms:
yield Tag(interpreter, abi, platform_)
@@ -251,7 +251,7 @@ def generic_tags(
interpreter = "".join([interp_name, interp_version])
if abis is None:
abis = _generic_abi()
- platforms = list(platforms or _platform_tags())
+ platforms = list(platforms or platform_tags())
abis = list(abis)
if "none" not in abis:
abis.append("none")
@@ -268,11 +268,11 @@ def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]:
all previous versions of that major version.
"""
if len(py_version) > 1:
- yield "py{version}".format(version=_version_nodot(py_version[:2]))
- yield "py{major}".format(major=py_version[0])
+ yield f"py{_version_nodot(py_version[:2])}"
+ yield f"py{py_version[0]}"
if len(py_version) > 1:
for minor in range(py_version[1] - 1, -1, -1):
- yield "py{version}".format(version=_version_nodot((py_version[0], minor)))
+ yield f"py{_version_nodot((py_version[0], minor))}"
def compatible_tags(
@@ -290,7 +290,7 @@ def compatible_tags(
"""
if not python_version:
python_version = sys.version_info[:2]
- platforms = list(platforms or _platform_tags())
+ platforms = list(platforms or platform_tags())
for version in _py_interpreter_range(python_version):
for platform_ in platforms:
yield Tag(version, "none", platform_)
@@ -431,7 +431,7 @@ def _generic_platforms() -> Iterator[str]:
yield _normalize_string(sysconfig.get_platform())
-def _platform_tags() -> Iterator[str]:
+def platform_tags() -> Iterator[str]:
"""
Provides the platform tags for this installation.
"""
@@ -481,4 +481,7 @@ def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
else:
yield from generic_tags()
- yield from compatible_tags()
+ if interp_name == "pp":
+ yield from compatible_tags(interpreter="pp3")
+ else:
+ yield from compatible_tags()
diff --git a/src/pip/_vendor/pep517.pyi b/src/pip/_vendor/pep517.pyi
deleted file mode 100644
index d1ce81029..000000000
--- a/src/pip/_vendor/pep517.pyi
+++ /dev/null
@@ -1 +0,0 @@
-from pep517 import * \ No newline at end of file
diff --git a/src/pip/_vendor/pep517/__init__.py b/src/pip/_vendor/pep517/__init__.py
deleted file mode 100644
index 2b6b88567..000000000
--- a/src/pip/_vendor/pep517/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""Wrappers to build Python packages using PEP 517 hooks
-"""
-
-__version__ = '0.12.0'
-
-from .wrappers import * # noqa: F401, F403
diff --git a/src/pip/_vendor/pep517/build.py b/src/pip/_vendor/pep517/build.py
deleted file mode 100644
index bc463b2ba..000000000
--- a/src/pip/_vendor/pep517/build.py
+++ /dev/null
@@ -1,127 +0,0 @@
-"""Build a project using PEP 517 hooks.
-"""
-import argparse
-import io
-import logging
-import os
-import shutil
-
-from .envbuild import BuildEnvironment
-from .wrappers import Pep517HookCaller
-from .dirtools import tempdir, mkdir_p
-from .compat import FileNotFoundError, toml_load
-
-log = logging.getLogger(__name__)
-
-
-def validate_system(system):
- """
- Ensure build system has the requisite fields.
- """
- required = {'requires', 'build-backend'}
- if not (required <= set(system)):
- message = "Missing required fields: {missing}".format(
- missing=required-set(system),
- )
- raise ValueError(message)
-
-
-def load_system(source_dir):
- """
- Load the build system from a source dir (pyproject.toml).
- """
- pyproject = os.path.join(source_dir, 'pyproject.toml')
- with io.open(pyproject, 'rb') as f:
- pyproject_data = toml_load(f)
- return pyproject_data['build-system']
-
-
-def compat_system(source_dir):
- """
- Given a source dir, attempt to get a build system backend
- and requirements from pyproject.toml. Fallback to
- setuptools but only if the file was not found or a build
- system was not indicated.
- """
- try:
- system = load_system(source_dir)
- except (FileNotFoundError, KeyError):
- system = {}
- system.setdefault(
- 'build-backend',
- 'setuptools.build_meta:__legacy__',
- )
- system.setdefault('requires', ['setuptools', 'wheel'])
- return system
-
-
-def _do_build(hooks, env, dist, dest):
- get_requires_name = 'get_requires_for_build_{dist}'.format(**locals())
- get_requires = getattr(hooks, get_requires_name)
- reqs = get_requires({})
- log.info('Got build requires: %s', reqs)
-
- env.pip_install(reqs)
- log.info('Installed dynamic build dependencies')
-
- with tempdir() as td:
- log.info('Trying to build %s in %s', dist, td)
- build_name = 'build_{dist}'.format(**locals())
- build = getattr(hooks, build_name)
- filename = build(td, {})
- source = os.path.join(td, filename)
- shutil.move(source, os.path.join(dest, os.path.basename(filename)))
-
-
-def build(source_dir, dist, dest=None, system=None):
- system = system or load_system(source_dir)
- dest = os.path.join(source_dir, dest or 'dist')
- mkdir_p(dest)
-
- validate_system(system)
- hooks = Pep517HookCaller(
- source_dir, system['build-backend'], system.get('backend-path')
- )
-
- with BuildEnvironment() as env:
- env.pip_install(system['requires'])
- _do_build(hooks, env, dist, dest)
-
-
-parser = argparse.ArgumentParser()
-parser.add_argument(
- 'source_dir',
- help="A directory containing pyproject.toml",
-)
-parser.add_argument(
- '--binary', '-b',
- action='store_true',
- default=False,
-)
-parser.add_argument(
- '--source', '-s',
- action='store_true',
- default=False,
-)
-parser.add_argument(
- '--out-dir', '-o',
- help="Destination in which to save the builds relative to source dir",
-)
-
-
-def main(args):
- log.warning('pep517.build is deprecated. '
- 'Consider switching to https://pypi.org/project/build/')
-
- # determine which dists to build
- dists = list(filter(None, (
- 'sdist' if args.source or not args.binary else None,
- 'wheel' if args.binary or not args.source else None,
- )))
-
- for dist in dists:
- build(args.source_dir, dist, args.out_dir)
-
-
-if __name__ == '__main__':
- main(parser.parse_args())
diff --git a/src/pip/_vendor/pep517/check.py b/src/pip/_vendor/pep517/check.py
deleted file mode 100644
index bf3c72264..000000000
--- a/src/pip/_vendor/pep517/check.py
+++ /dev/null
@@ -1,207 +0,0 @@
-"""Check a project and backend by attempting to build using PEP 517 hooks.
-"""
-import argparse
-import io
-import logging
-import os
-from os.path import isfile, join as pjoin
-import shutil
-from subprocess import CalledProcessError
-import sys
-import tarfile
-from tempfile import mkdtemp
-import zipfile
-
-from .colorlog import enable_colourful_output
-from .compat import TOMLDecodeError, toml_load
-from .envbuild import BuildEnvironment
-from .wrappers import Pep517HookCaller
-
-log = logging.getLogger(__name__)
-
-
-def check_build_sdist(hooks, build_sys_requires):
- with BuildEnvironment() as env:
- try:
- env.pip_install(build_sys_requires)
- log.info('Installed static build dependencies')
- except CalledProcessError:
- log.error('Failed to install static build dependencies')
- return False
-
- try:
- reqs = hooks.get_requires_for_build_sdist({})
- log.info('Got build requires: %s', reqs)
- except Exception:
- log.error('Failure in get_requires_for_build_sdist', exc_info=True)
- return False
-
- try:
- env.pip_install(reqs)
- log.info('Installed dynamic build dependencies')
- except CalledProcessError:
- log.error('Failed to install dynamic build dependencies')
- return False
-
- td = mkdtemp()
- log.info('Trying to build sdist in %s', td)
- try:
- try:
- filename = hooks.build_sdist(td, {})
- log.info('build_sdist returned %r', filename)
- except Exception:
- log.info('Failure in build_sdist', exc_info=True)
- return False
-
- if not filename.endswith('.tar.gz'):
- log.error(
- "Filename %s doesn't have .tar.gz extension", filename)
- return False
-
- path = pjoin(td, filename)
- if isfile(path):
- log.info("Output file %s exists", path)
- else:
- log.error("Output file %s does not exist", path)
- return False
-
- if tarfile.is_tarfile(path):
- log.info("Output file is a tar file")
- else:
- log.error("Output file is not a tar file")
- return False
-
- finally:
- shutil.rmtree(td)
-
- return True
-
-
-def check_build_wheel(hooks, build_sys_requires):
- with BuildEnvironment() as env:
- try:
- env.pip_install(build_sys_requires)
- log.info('Installed static build dependencies')
- except CalledProcessError:
- log.error('Failed to install static build dependencies')
- return False
-
- try:
- reqs = hooks.get_requires_for_build_wheel({})
- log.info('Got build requires: %s', reqs)
- except Exception:
- log.error('Failure in get_requires_for_build_sdist', exc_info=True)
- return False
-
- try:
- env.pip_install(reqs)
- log.info('Installed dynamic build dependencies')
- except CalledProcessError:
- log.error('Failed to install dynamic build dependencies')
- return False
-
- td = mkdtemp()
- log.info('Trying to build wheel in %s', td)
- try:
- try:
- filename = hooks.build_wheel(td, {})
- log.info('build_wheel returned %r', filename)
- except Exception:
- log.info('Failure in build_wheel', exc_info=True)
- return False
-
- if not filename.endswith('.whl'):
- log.error("Filename %s doesn't have .whl extension", filename)
- return False
-
- path = pjoin(td, filename)
- if isfile(path):
- log.info("Output file %s exists", path)
- else:
- log.error("Output file %s does not exist", path)
- return False
-
- if zipfile.is_zipfile(path):
- log.info("Output file is a zip file")
- else:
- log.error("Output file is not a zip file")
- return False
-
- finally:
- shutil.rmtree(td)
-
- return True
-
-
-def check(source_dir):
- pyproject = pjoin(source_dir, 'pyproject.toml')
- if isfile(pyproject):
- log.info('Found pyproject.toml')
- else:
- log.error('Missing pyproject.toml')
- return False
-
- try:
- with io.open(pyproject, 'rb') as f:
- pyproject_data = toml_load(f)
- # Ensure the mandatory data can be loaded
- buildsys = pyproject_data['build-system']
- requires = buildsys['requires']
- backend = buildsys['build-backend']
- backend_path = buildsys.get('backend-path')
- log.info('Loaded pyproject.toml')
- except (TOMLDecodeError, KeyError):
- log.error("Invalid pyproject.toml", exc_info=True)
- return False
-
- hooks = Pep517HookCaller(source_dir, backend, backend_path)
-
- sdist_ok = check_build_sdist(hooks, requires)
- wheel_ok = check_build_wheel(hooks, requires)
-
- if not sdist_ok:
- log.warning('Sdist checks failed; scroll up to see')
- if not wheel_ok:
- log.warning('Wheel checks failed')
-
- return sdist_ok
-
-
-def main(argv=None):
- log.warning('pep517.check is deprecated. '
- 'Consider switching to https://pypi.org/project/build/')
-
- ap = argparse.ArgumentParser()
- ap.add_argument(
- 'source_dir',
- help="A directory containing pyproject.toml")
- args = ap.parse_args(argv)
-
- enable_colourful_output()
-
- ok = check(args.source_dir)
-
- if ok:
- print(ansi('Checks passed', 'green'))
- else:
- print(ansi('Checks failed', 'red'))
- sys.exit(1)
-
-
-ansi_codes = {
- 'reset': '\x1b[0m',
- 'bold': '\x1b[1m',
- 'red': '\x1b[31m',
- 'green': '\x1b[32m',
-}
-
-
-def ansi(s, attr):
- if os.name != 'nt' and sys.stdout.isatty():
- return ansi_codes[attr] + str(s) + ansi_codes['reset']
- else:
- return str(s)
-
-
-if __name__ == '__main__':
- main()
diff --git a/src/pip/_vendor/pep517/colorlog.py b/src/pip/_vendor/pep517/colorlog.py
deleted file mode 100644
index 69c8a59d3..000000000
--- a/src/pip/_vendor/pep517/colorlog.py
+++ /dev/null
@@ -1,115 +0,0 @@
-"""Nicer log formatting with colours.
-
-Code copied from Tornado, Apache licensed.
-"""
-# Copyright 2012 Facebook
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import logging
-import sys
-
-try:
- import curses
-except ImportError:
- curses = None
-
-
-def _stderr_supports_color():
- color = False
- if curses and hasattr(sys.stderr, 'isatty') and sys.stderr.isatty():
- try:
- curses.setupterm()
- if curses.tigetnum("colors") > 0:
- color = True
- except Exception:
- pass
- return color
-
-
-class LogFormatter(logging.Formatter):
- """Log formatter with colour support
- """
- DEFAULT_COLORS = {
- logging.INFO: 2, # Green
- logging.WARNING: 3, # Yellow
- logging.ERROR: 1, # Red
- logging.CRITICAL: 1,
- }
-
- def __init__(self, color=True, datefmt=None):
- r"""
- :arg bool color: Enables color support.
- :arg string fmt: Log message format.
- It will be applied to the attributes dict of log records. The
- text between ``%(color)s`` and ``%(end_color)s`` will be colored
- depending on the level if color support is on.
- :arg dict colors: color mappings from logging level to terminal color
- code
- :arg string datefmt: Datetime format.
- Used for formatting ``(asctime)`` placeholder in ``prefix_fmt``.
- .. versionchanged:: 3.2
- Added ``fmt`` and ``datefmt`` arguments.
- """
- logging.Formatter.__init__(self, datefmt=datefmt)
- self._colors = {}
- if color and _stderr_supports_color():
- # The curses module has some str/bytes confusion in
- # python3. Until version 3.2.3, most methods return
- # bytes, but only accept strings. In addition, we want to
- # output these strings with the logging module, which
- # works with unicode strings. The explicit calls to
- # unicode() below are harmless in python2 but will do the
- # right conversion in python 3.
- fg_color = (curses.tigetstr("setaf") or
- curses.tigetstr("setf") or "")
- if (3, 0) < sys.version_info < (3, 2, 3):
- fg_color = str(fg_color, "ascii")
-
- for levelno, code in self.DEFAULT_COLORS.items():
- self._colors[levelno] = str(
- curses.tparm(fg_color, code), "ascii")
- self._normal = str(curses.tigetstr("sgr0"), "ascii")
-
- scr = curses.initscr()
- self.termwidth = scr.getmaxyx()[1]
- curses.endwin()
- else:
- self._normal = ''
- # Default width is usually 80, but too wide is
- # worse than too narrow
- self.termwidth = 70
-
- def formatMessage(self, record):
- mlen = len(record.message)
- right_text = '{initial}-{name}'.format(initial=record.levelname[0],
- name=record.name)
- if mlen + len(right_text) < self.termwidth:
- space = ' ' * (self.termwidth - (mlen + len(right_text)))
- else:
- space = ' '
-
- if record.levelno in self._colors:
- start_color = self._colors[record.levelno]
- end_color = self._normal
- else:
- start_color = end_color = ''
-
- return record.message + space + start_color + right_text + end_color
-
-
-def enable_colourful_output(level=logging.INFO):
- handler = logging.StreamHandler()
- handler.setFormatter(LogFormatter())
- logging.root.addHandler(handler)
- logging.root.setLevel(level)
diff --git a/src/pip/_vendor/pep517/compat.py b/src/pip/_vendor/pep517/compat.py
deleted file mode 100644
index 730ef5ffa..000000000
--- a/src/pip/_vendor/pep517/compat.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""Python 2/3 compatibility"""
-import io
-import json
-import sys
-
-
-# Handle reading and writing JSON in UTF-8, on Python 3 and 2.
-
-if sys.version_info[0] >= 3:
- # Python 3
- def write_json(obj, path, **kwargs):
- with open(path, 'w', encoding='utf-8') as f:
- json.dump(obj, f, **kwargs)
-
- def read_json(path):
- with open(path, 'r', encoding='utf-8') as f:
- return json.load(f)
-
-else:
- # Python 2
- def write_json(obj, path, **kwargs):
- with open(path, 'wb') as f:
- json.dump(obj, f, encoding='utf-8', **kwargs)
-
- def read_json(path):
- with open(path, 'rb') as f:
- return json.load(f)
-
-
-# FileNotFoundError
-
-try:
- FileNotFoundError = FileNotFoundError
-except NameError:
- FileNotFoundError = IOError
-
-
-if sys.version_info < (3, 6):
- from toml import load as _toml_load # noqa: F401
-
- def toml_load(f):
- w = io.TextIOWrapper(f, encoding="utf8", newline="")
- try:
- return _toml_load(w)
- finally:
- w.detach()
-
- from toml import TomlDecodeError as TOMLDecodeError # noqa: F401
-else:
- from pip._vendor.tomli import load as toml_load # noqa: F401
- from pip._vendor.tomli import TOMLDecodeError # noqa: F401
diff --git a/src/pip/_vendor/pep517/dirtools.py b/src/pip/_vendor/pep517/dirtools.py
deleted file mode 100644
index 58c6ca0c5..000000000
--- a/src/pip/_vendor/pep517/dirtools.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import os
-import io
-import contextlib
-import tempfile
-import shutil
-import errno
-import zipfile
-
-
-@contextlib.contextmanager
-def tempdir():
- """Create a temporary directory in a context manager."""
- td = tempfile.mkdtemp()
- try:
- yield td
- finally:
- shutil.rmtree(td)
-
-
-def mkdir_p(*args, **kwargs):
- """Like `mkdir`, but does not raise an exception if the
- directory already exists.
- """
- try:
- return os.mkdir(*args, **kwargs)
- except OSError as exc:
- if exc.errno != errno.EEXIST:
- raise
-
-
-def dir_to_zipfile(root):
- """Construct an in-memory zip file for a directory."""
- buffer = io.BytesIO()
- zip_file = zipfile.ZipFile(buffer, 'w')
- for root, dirs, files in os.walk(root):
- for path in dirs:
- fs_path = os.path.join(root, path)
- rel_path = os.path.relpath(fs_path, root)
- zip_file.writestr(rel_path + '/', '')
- for path in files:
- fs_path = os.path.join(root, path)
- rel_path = os.path.relpath(fs_path, root)
- zip_file.write(fs_path, rel_path)
- return zip_file
diff --git a/src/pip/_vendor/pep517/envbuild.py b/src/pip/_vendor/pep517/envbuild.py
deleted file mode 100644
index fe8873c64..000000000
--- a/src/pip/_vendor/pep517/envbuild.py
+++ /dev/null
@@ -1,171 +0,0 @@
-"""Build wheels/sdists by installing build deps to a temporary environment.
-"""
-
-import io
-import os
-import logging
-import shutil
-from subprocess import check_call
-import sys
-from sysconfig import get_paths
-from tempfile import mkdtemp
-
-from .compat import toml_load
-from .wrappers import Pep517HookCaller, LoggerWrapper
-
-log = logging.getLogger(__name__)
-
-
-def _load_pyproject(source_dir):
- with io.open(
- os.path.join(source_dir, 'pyproject.toml'),
- 'rb',
- ) as f:
- pyproject_data = toml_load(f)
- buildsys = pyproject_data['build-system']
- return (
- buildsys['requires'],
- buildsys['build-backend'],
- buildsys.get('backend-path'),
- )
-
-
-class BuildEnvironment(object):
- """Context manager to install build deps in a simple temporary environment
-
- Based on code I wrote for pip, which is MIT licensed.
- """
- # Copyright (c) 2008-2016 The pip developers (see AUTHORS.txt file)
- #
- # 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.
-
- path = None
-
- def __init__(self, cleanup=True):
- self._cleanup = cleanup
-
- def __enter__(self):
- self.path = mkdtemp(prefix='pep517-build-env-')
- log.info('Temporary build environment: %s', self.path)
-
- self.save_path = os.environ.get('PATH', None)
- self.save_pythonpath = os.environ.get('PYTHONPATH', None)
-
- install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix'
- install_dirs = get_paths(install_scheme, vars={
- 'base': self.path,
- 'platbase': self.path,
- })
-
- scripts = install_dirs['scripts']
- if self.save_path:
- os.environ['PATH'] = scripts + os.pathsep + self.save_path
- else:
- os.environ['PATH'] = scripts + os.pathsep + os.defpath
-
- if install_dirs['purelib'] == install_dirs['platlib']:
- lib_dirs = install_dirs['purelib']
- else:
- lib_dirs = install_dirs['purelib'] + os.pathsep + \
- install_dirs['platlib']
- if self.save_pythonpath:
- os.environ['PYTHONPATH'] = lib_dirs + os.pathsep + \
- self.save_pythonpath
- else:
- os.environ['PYTHONPATH'] = lib_dirs
-
- return self
-
- def pip_install(self, reqs):
- """Install dependencies into this env by calling pip in a subprocess"""
- if not reqs:
- return
- log.info('Calling pip to install %s', reqs)
- cmd = [
- sys.executable, '-m', 'pip', 'install', '--ignore-installed',
- '--prefix', self.path] + list(reqs)
- check_call(
- cmd,
- stdout=LoggerWrapper(log, logging.INFO),
- stderr=LoggerWrapper(log, logging.ERROR),
- )
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- needs_cleanup = (
- self._cleanup and
- self.path is not None and
- os.path.isdir(self.path)
- )
- if needs_cleanup:
- shutil.rmtree(self.path)
-
- if self.save_path is None:
- os.environ.pop('PATH', None)
- else:
- os.environ['PATH'] = self.save_path
-
- if self.save_pythonpath is None:
- os.environ.pop('PYTHONPATH', None)
- else:
- os.environ['PYTHONPATH'] = self.save_pythonpath
-
-
-def build_wheel(source_dir, wheel_dir, config_settings=None):
- """Build a wheel from a source directory using PEP 517 hooks.
-
- :param str source_dir: Source directory containing pyproject.toml
- :param str wheel_dir: Target directory to create wheel in
- :param dict config_settings: Options to pass to build backend
-
- This is a blocking function which will run pip in a subprocess to install
- build requirements.
- """
- if config_settings is None:
- config_settings = {}
- requires, backend, backend_path = _load_pyproject(source_dir)
- hooks = Pep517HookCaller(source_dir, backend, backend_path)
-
- with BuildEnvironment() as env:
- env.pip_install(requires)
- reqs = hooks.get_requires_for_build_wheel(config_settings)
- env.pip_install(reqs)
- return hooks.build_wheel(wheel_dir, config_settings)
-
-
-def build_sdist(source_dir, sdist_dir, config_settings=None):
- """Build an sdist from a source directory using PEP 517 hooks.
-
- :param str source_dir: Source directory containing pyproject.toml
- :param str sdist_dir: Target directory to place sdist in
- :param dict config_settings: Options to pass to build backend
-
- This is a blocking function which will run pip in a subprocess to install
- build requirements.
- """
- if config_settings is None:
- config_settings = {}
- requires, backend, backend_path = _load_pyproject(source_dir)
- hooks = Pep517HookCaller(source_dir, backend, backend_path)
-
- with BuildEnvironment() as env:
- env.pip_install(requires)
- reqs = hooks.get_requires_for_build_sdist(config_settings)
- env.pip_install(reqs)
- return hooks.build_sdist(sdist_dir, config_settings)
diff --git a/src/pip/_vendor/pep517/meta.py b/src/pip/_vendor/pep517/meta.py
deleted file mode 100644
index d525de5c6..000000000
--- a/src/pip/_vendor/pep517/meta.py
+++ /dev/null
@@ -1,92 +0,0 @@
-"""Build metadata for a project using PEP 517 hooks.
-"""
-import argparse
-import logging
-import os
-import shutil
-import functools
-
-try:
- import importlib.metadata as imp_meta
-except ImportError:
- import importlib_metadata as imp_meta
-
-try:
- from zipfile import Path
-except ImportError:
- from zipp import Path
-
-from .envbuild import BuildEnvironment
-from .wrappers import Pep517HookCaller, quiet_subprocess_runner
-from .dirtools import tempdir, mkdir_p, dir_to_zipfile
-from .build import validate_system, load_system, compat_system
-
-log = logging.getLogger(__name__)
-
-
-def _prep_meta(hooks, env, dest):
- reqs = hooks.get_requires_for_build_wheel({})
- log.info('Got build requires: %s', reqs)
-
- env.pip_install(reqs)
- log.info('Installed dynamic build dependencies')
-
- with tempdir() as td:
- log.info('Trying to build metadata in %s', td)
- filename = hooks.prepare_metadata_for_build_wheel(td, {})
- source = os.path.join(td, filename)
- shutil.move(source, os.path.join(dest, os.path.basename(filename)))
-
-
-def build(source_dir='.', dest=None, system=None):
- system = system or load_system(source_dir)
- dest = os.path.join(source_dir, dest or 'dist')
- mkdir_p(dest)
- validate_system(system)
- hooks = Pep517HookCaller(
- source_dir, system['build-backend'], system.get('backend-path')
- )
-
- with hooks.subprocess_runner(quiet_subprocess_runner):
- with BuildEnvironment() as env:
- env.pip_install(system['requires'])
- _prep_meta(hooks, env, dest)
-
-
-def build_as_zip(builder=build):
- with tempdir() as out_dir:
- builder(dest=out_dir)
- return dir_to_zipfile(out_dir)
-
-
-def load(root):
- """
- Given a source directory (root) of a package,
- return an importlib.metadata.Distribution object
- with metadata build from that package.
- """
- root = os.path.expanduser(root)
- system = compat_system(root)
- builder = functools.partial(build, source_dir=root, system=system)
- path = Path(build_as_zip(builder))
- return imp_meta.PathDistribution(path)
-
-
-parser = argparse.ArgumentParser()
-parser.add_argument(
- 'source_dir',
- help="A directory containing pyproject.toml",
-)
-parser.add_argument(
- '--out-dir', '-o',
- help="Destination in which to save the builds relative to source dir",
-)
-
-
-def main():
- args = parser.parse_args()
- build(args.source_dir, args.out_dir)
-
-
-if __name__ == '__main__':
- main()
diff --git a/src/pip/_vendor/pkg_resources/LICENSE b/src/pip/_vendor/pkg_resources/LICENSE
index 6e0693b4b..353924be0 100644
--- a/src/pip/_vendor/pkg_resources/LICENSE
+++ b/src/pip/_vendor/pkg_resources/LICENSE
@@ -1,19 +1,19 @@
-Copyright (C) 2016 Jason R Coombs <jaraco@jaraco.com>
+Copyright Jason R. Coombs
-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:
+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 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.
+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.
diff --git a/src/pip/_vendor/pkg_resources/__init__.py b/src/pip/_vendor/pkg_resources/__init__.py
index 4cd562cf9..0ec74f8a6 100644
--- a/src/pip/_vendor/pkg_resources/__init__.py
+++ b/src/pip/_vendor/pkg_resources/__init__.py
@@ -1,4 +1,3 @@
-# coding: utf-8
"""
Package resource API
--------------------
@@ -15,8 +14,6 @@ The package resource API is designed to work with normal filesystem packages,
method.
"""
-from __future__ import absolute_import
-
import sys
import os
import io
@@ -41,6 +38,7 @@ import itertools
import inspect
import ntpath
import posixpath
+import importlib
from pkgutil import get_importer
try:
@@ -54,9 +52,6 @@ try:
except NameError:
FileExistsError = OSError
-from pip._vendor import six
-from pip._vendor.six.moves import urllib, map, filter
-
# capture these to bypass sandboxing
from os import utime
try:
@@ -76,26 +71,23 @@ try:
except ImportError:
importlib_machinery = None
-from . import py31compat
+from pip._internal.utils._jaraco_text import (
+ yield_lines,
+ drop_comment,
+ join_continuation,
+)
+
from pip._vendor import platformdirs
from pip._vendor import packaging
__import__('pip._vendor.packaging.version')
__import__('pip._vendor.packaging.specifiers')
__import__('pip._vendor.packaging.requirements')
__import__('pip._vendor.packaging.markers')
+__import__('pip._vendor.packaging.utils')
-
-__metaclass__ = type
-
-
-if (3, 0) < sys.version_info < (3, 5):
+if sys.version_info < (3, 5):
raise RuntimeError("Python 3.5 or later is required")
-if six.PY2:
- # Those builtin exceptions are only defined in Python 3
- PermissionError = None
- NotADirectoryError = None
-
# declare some globals that will be defined later to
# satisfy the linters.
require = None
@@ -128,6 +120,11 @@ def parse_version(v):
try:
return packaging.version.Version(v)
except packaging.version.InvalidVersion:
+ warnings.warn(
+ f"{v} is an invalid version and will not be supported in "
+ "a future release",
+ PkgResourcesDeprecationWarning,
+ )
return packaging.version.LegacyVersion(v)
@@ -178,10 +175,10 @@ def get_supported_platform():
"""Return this platform's maximum compatible version.
distutils.util.get_platform() normally reports the minimum version
- of Mac OS X that would be required to *use* extensions produced by
+ of macOS that would be required to *use* extensions produced by
distutils. But what we want when checking compatibility is to know the
- version of Mac OS X that we are *running*. To allow usage of packages that
- explicitly require a newer version of Mac OS X, we must also know the
+ version of macOS that we are *running*. To allow usage of packages that
+ explicitly require a newer version of macOS, we must also know the
current version of the OS.
If this condition occurs for any other platform with a version in its
@@ -191,9 +188,9 @@ def get_supported_platform():
m = macosVersionString.match(plat)
if m is not None and sys.platform == "darwin":
try:
- plat = 'macosx-%s-%s' % ('.'.join(_macosx_vers()[:2]), m.group(3))
+ plat = 'macosx-%s-%s' % ('.'.join(_macos_vers()[:2]), m.group(3))
except ValueError:
- # not Mac OS X
+ # not macOS
pass
return plat
@@ -364,7 +361,7 @@ def get_provider(moduleOrReq):
return _find_adapter(_provider_factories, loader)(module)
-def _macosx_vers(_cache=[]):
+def _macos_vers(_cache=[]):
if not _cache:
version = platform.mac_ver()[0]
# fallback for MacPorts
@@ -380,7 +377,7 @@ def _macosx_vers(_cache=[]):
return _cache[0]
-def _macosx_arch(machine):
+def _macos_arch(machine):
return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine)
@@ -388,18 +385,18 @@ def get_build_platform():
"""Return this platform's string for platform-specific distributions
XXX Currently this is the same as ``distutils.util.get_platform()``, but it
- needs some hacks for Linux and Mac OS X.
+ needs some hacks for Linux and macOS.
"""
from sysconfig import get_platform
plat = get_platform()
if sys.platform == "darwin" and not plat.startswith('macosx-'):
try:
- version = _macosx_vers()
+ version = _macos_vers()
machine = os.uname()[4].replace(" ", "_")
return "macosx-%d.%d-%s" % (
int(version[0]), int(version[1]),
- _macosx_arch(machine),
+ _macos_arch(machine),
)
except ValueError:
# if someone is running a non-Mac darwin system, this will fall
@@ -425,7 +422,7 @@ def compatible_platforms(provided, required):
# easy case
return True
- # Mac OS X special cases
+ # macOS special cases
reqMac = macosVersionString.match(required)
if reqMac:
provMac = macosVersionString.match(provided)
@@ -434,7 +431,7 @@ def compatible_platforms(provided, required):
if not provMac:
# this is backwards compatibility for packages built before
# setuptools 0.6. All packages built after this point will
- # use the new macosx designation.
+ # use the new macOS designation.
provDarwin = darwinVersionString.match(provided)
if provDarwin:
dversion = int(provDarwin.group(1))
@@ -442,7 +439,7 @@ def compatible_platforms(provided, required):
if dversion == 7 and macosversion >= "10.3" or \
dversion == 8 and macosversion >= "10.4":
return True
- # egg isn't macosx or legacy darwin
+ # egg isn't macOS or legacy darwin
return False
# are they the same major version and machine type?
@@ -475,7 +472,7 @@ run_main = run_script
def get_distribution(dist):
"""Return a current distribution object for a Requirement or string"""
- if isinstance(dist, six.string_types):
+ if isinstance(dist, str):
dist = Requirement.parse(dist)
if isinstance(dist, Requirement):
dist = get_provider(dist)
@@ -558,6 +555,7 @@ class WorkingSet:
self.entries = []
self.entry_keys = {}
self.by_key = {}
+ self.normalized_to_canonical_keys = {}
self.callbacks = []
if entries is None:
@@ -638,6 +636,14 @@ class WorkingSet:
is returned.
"""
dist = self.by_key.get(req.key)
+
+ if dist is None:
+ canonical_key = self.normalized_to_canonical_keys.get(req.key)
+
+ if canonical_key is not None:
+ req.key = canonical_key
+ dist = self.by_key.get(canonical_key)
+
if dist is not None and dist not in req:
# XXX add more info
raise VersionConflict(dist, req)
@@ -706,13 +712,16 @@ class WorkingSet:
return
self.by_key[dist.key] = dist
+ normalized_name = packaging.utils.canonicalize_name(dist.key)
+ self.normalized_to_canonical_keys[normalized_name] = dist.key
if dist.key not in keys:
keys.append(dist.key)
if dist.key not in keys2:
keys2.append(dist.key)
self._added_new(dist)
- def resolve(self, requirements, env=None, installer=None,
+ # FIXME: 'WorkingSet.resolve' is too complex (11)
+ def resolve(self, requirements, env=None, installer=None, # noqa: C901
replace_conflicting=False, extras=None):
"""List all distributions needed to (recursively) meet `requirements`
@@ -925,14 +934,15 @@ class WorkingSet:
def __getstate__(self):
return (
self.entries[:], self.entry_keys.copy(), self.by_key.copy(),
- self.callbacks[:]
+ self.normalized_to_canonical_keys.copy(), self.callbacks[:]
)
- def __setstate__(self, e_k_b_c):
- entries, keys, by_key, callbacks = e_k_b_c
+ def __setstate__(self, e_k_b_n_c):
+ entries, keys, by_key, normalized_to_canonical_keys, callbacks = e_k_b_n_c
self.entries = entries[:]
self.entry_keys = keys.copy()
self.by_key = by_key.copy()
+ self.normalized_to_canonical_keys = normalized_to_canonical_keys.copy()
self.callbacks = callbacks[:]
@@ -1234,12 +1244,13 @@ class ResourceManager:
mode = os.stat(path).st_mode
if mode & stat.S_IWOTH or mode & stat.S_IWGRP:
msg = (
- "%s is writable by group/others and vulnerable to attack "
- "when "
- "used with get_resource_filename. Consider a more secure "
+ "Extraction path is writable by group/others "
+ "and vulnerable to attack when "
+ "used with get_resource_filename ({path}). "
+ "Consider a more secure "
"location (set with .set_extraction_path or the "
- "PYTHON_EGG_CACHE environment variable)." % path
- )
+ "PYTHON_EGG_CACHE environment variable)."
+ ).format(**locals())
warnings.warn(msg, UserWarning)
def postprocess(self, tempname, filename):
@@ -1377,7 +1388,7 @@ def evaluate_marker(text, extra=None):
marker = packaging.markers.Marker(text)
return marker.evaluate()
except packaging.markers.InvalidMarker as e:
- raise SyntaxError(e)
+ raise SyntaxError(e) from e
class NullProvider:
@@ -1418,8 +1429,6 @@ class NullProvider:
return ""
path = self._get_metadata_path(name)
value = self._get(path)
- if six.PY2:
- return value
try:
return value.decode('utf-8')
except UnicodeDecodeError as exc:
@@ -1457,7 +1466,8 @@ class NullProvider:
script_filename = self._fn(self.egg_info, script)
namespace['__file__'] = script_filename
if os.path.exists(script_filename):
- source = open(script_filename).read()
+ with open(script_filename) as fid:
+ source = fid.read()
code = compile(source, script_filename, 'exec')
exec(code, namespace, namespace)
else:
@@ -1493,7 +1503,7 @@ class NullProvider:
def _validate_resource_path(path):
"""
Validate the resource paths according to the docs.
- https://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access
+ https://setuptools.pypa.io/en/latest/pkg_resources.html#basic-resource-access
>>> warned = getfixture('recwarn')
>>> warnings.simplefilter('always')
@@ -1575,26 +1585,35 @@ is not allowed.
register_loader_type(object, NullProvider)
+def _parents(path):
+ """
+ yield all parents of path including path
+ """
+ last = None
+ while path != last:
+ yield path
+ last = path
+ path, _ = os.path.split(path)
+
+
class EggProvider(NullProvider):
"""Provider based on a virtual filesystem"""
def __init__(self, module):
- NullProvider.__init__(self, module)
+ super().__init__(module)
self._setup_prefix()
def _setup_prefix(self):
- # we assume here that our metadata may be nested inside a "basket"
- # of multiple eggs; that's why we use module_path instead of .archive
- path = self.module_path
- old = None
- while path != old:
- if _is_egg_path(path):
- self.egg_name = os.path.basename(path)
- self.egg_info = os.path.join(path, 'EGG-INFO')
- self.egg_root = path
- break
- old = path
- path, base = os.path.split(path)
+ # Assume that metadata may be nested inside a "basket"
+ # of multiple eggs and use module_path instead of .archive.
+ eggs = filter(_is_egg_path, _parents(self.module_path))
+ egg = next(eggs, None)
+ egg and self._set_egg(egg)
+
+ def _set_egg(self, path):
+ self.egg_name = os.path.basename(path)
+ self.egg_info = os.path.join(path, 'EGG-INFO')
+ self.egg_root = path
class DefaultProvider(EggProvider):
@@ -1701,7 +1720,7 @@ class ZipProvider(EggProvider):
_zip_manifests = MemoizedZipManifests()
def __init__(self, module):
- EggProvider.__init__(self, module)
+ super().__init__(module)
self.zip_pre = self.loader.archive + os.sep
def _zipinfo_name(self, fspath):
@@ -1752,7 +1771,8 @@ class ZipProvider(EggProvider):
timestamp = time.mktime(date_time)
return timestamp, size
- def _extract_resource(self, manager, zip_path):
+ # FIXME: 'ZipProvider._extract_resource' is too complex (12)
+ def _extract_resource(self, manager, zip_path): # noqa: C901
if zip_path in self._index():
for name in self._index()[zip_path]:
@@ -1900,8 +1920,7 @@ class FileMetadata(EmptyProvider):
return metadata
def _warn_on_replacement(self, metadata):
- # Python 2.7 compat for: replacement_char = '�'
- replacement_char = b'\xef\xbf\xbd'.decode('utf-8')
+ replacement_char = '�'
if replacement_char in metadata:
tmpl = "{self.path} could not be properly decoded in UTF-8"
msg = tmpl.format(**locals())
@@ -1991,7 +2010,7 @@ def find_eggs_in_zip(importer, path_item, only=False):
dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath)
for dist in dists:
yield dist
- elif subitem.lower().endswith('.dist-info'):
+ elif subitem.lower().endswith(('.dist-info', '.egg-info')):
subpath = os.path.join(path_item, subitem)
submeta = EggMetadata(zipimport.zipimporter(subpath))
submeta.egg_info = subpath
@@ -2015,7 +2034,7 @@ def _by_version_descending(names):
>>> names = 'bar', 'foo', 'Python-2.7.10.egg', 'Python-2.7.2.egg'
>>> _by_version_descending(names)
- ['Python-2.7.10.egg', 'Python-2.7.2.egg', 'foo', 'bar']
+ ['Python-2.7.10.egg', 'Python-2.7.2.egg', 'bar', 'foo']
>>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.egg'
>>> _by_version_descending(names)
['Setuptools-1.2.3.egg', 'Setuptools-1.2.3b1.egg']
@@ -2023,13 +2042,22 @@ def _by_version_descending(names):
>>> _by_version_descending(names)
['Setuptools-1.2.3.post1.egg', 'Setuptools-1.2.3b1.egg']
"""
+ def try_parse(name):
+ """
+ Attempt to parse as a version or return a null version.
+ """
+ try:
+ return packaging.version.Version(name)
+ except Exception:
+ return packaging.version.Version('0')
+
def _by_version(name):
"""
Parse each component of the filename
"""
name, ext = os.path.splitext(name)
parts = itertools.chain(name.split('-'), [ext])
- return [packaging.version.parse(part) for part in parts]
+ return [try_parse(part) for part in parts]
return sorted(names, key=_by_version, reverse=True)
@@ -2046,7 +2074,10 @@ def find_on_path(importer, path_item, only=False):
)
return
- entries = safe_listdir(path_item)
+ entries = (
+ os.path.join(path_item, child)
+ for child in safe_listdir(path_item)
+ )
# for performance, before sorting by version,
# screen entries for only those that will yield
@@ -2067,11 +2098,14 @@ def find_on_path(importer, path_item, only=False):
def dist_factory(path_item, entry, only):
- """
- Return a dist_factory for a path_item and entry
- """
+ """Return a dist_factory for the given entry."""
lower = entry.lower()
- is_meta = any(map(lower.endswith, ('.egg-info', '.dist-info')))
+ is_egg_info = lower.endswith('.egg-info')
+ is_dist_info = (
+ lower.endswith('.dist-info') and
+ os.path.isdir(os.path.join(path_item, entry))
+ )
+ is_meta = is_egg_info or is_dist_info
return (
distributions_from_metadata
if is_meta else
@@ -2093,8 +2127,6 @@ class NoDists:
"""
def __bool__(self):
return False
- if six.PY2:
- __nonzero__ = __bool__
def __call__(self, fullpath):
return iter(())
@@ -2111,12 +2143,7 @@ def safe_listdir(path):
except OSError as e:
# Ignore the directory if does not exist, not a directory or
# permission denied
- ignorable = (
- e.errno in (errno.ENOTDIR, errno.EACCES, errno.ENOENT)
- # Python 2 on Windows needs to be handled this way :(
- or getattr(e, "winerror", None) == 267
- )
- if not ignorable:
+ if e.errno not in (errno.ENOTDIR, errno.EACCES, errno.ENOENT):
raise
return ()
@@ -2195,10 +2222,16 @@ def _handle_ns(packageName, path_item):
if importer is None:
return None
- # capture warnings due to #1111
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- loader = importer.find_module(packageName)
+ # use find_spec (PEP 451) and fall-back to find_module (PEP 302)
+ try:
+ spec = importer.find_spec(packageName)
+ except AttributeError:
+ # capture warnings due to #1111
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ loader = importer.find_module(packageName)
+ else:
+ loader = spec.loader if spec else None
if loader is None:
return None
@@ -2214,7 +2247,7 @@ def _handle_ns(packageName, path_item):
if subpath is not None:
path = module.__path__
path.append(subpath)
- loader.load_module(packageName)
+ importlib.import_module(packageName)
_rebuild_mod_path(path, packageName, module)
return subpath
@@ -2270,8 +2303,8 @@ def declare_namespace(packageName):
__import__(parent)
try:
path = sys.modules[parent].__path__
- except AttributeError:
- raise TypeError("Not a package:", parent)
+ except AttributeError as e:
+ raise TypeError("Not a package:", parent) from e
# Track what packages are namespaces, so when new path items are added,
# they can be updated
@@ -2328,7 +2361,8 @@ register_namespace_handler(object, null_ns_handler)
def normalize_path(filename):
"""Normalize a file/dir name for comparison purposes"""
- return os.path.normcase(os.path.realpath(os.path.normpath(_cygwin_patch(filename))))
+ return os.path.normcase(os.path.realpath(os.path.normpath(
+ _cygwin_patch(filename))))
def _cygwin_patch(filename): # pragma: nocover
@@ -2354,7 +2388,15 @@ def _is_egg_path(path):
"""
Determine if given path appears to be an egg.
"""
- return path.lower().endswith('.egg')
+ return _is_zip_egg(path) or _is_unpacked_egg(path)
+
+
+def _is_zip_egg(path):
+ return (
+ path.lower().endswith('.egg') and
+ os.path.isfile(path) and
+ zipfile.is_zipfile(path)
+ )
def _is_unpacked_egg(path):
@@ -2362,7 +2404,7 @@ def _is_unpacked_egg(path):
Determine if given path appears to be an unpacked egg.
"""
return (
- _is_egg_path(path) and
+ path.lower().endswith('.egg') and
os.path.isfile(os.path.join(path, 'EGG-INFO', 'PKG-INFO'))
)
@@ -2375,20 +2417,6 @@ def _set_parent_ns(packageName):
setattr(sys.modules[parent], name, sys.modules[packageName])
-def yield_lines(strs):
- """Yield non-empty/non-comment lines of a string or sequence"""
- if isinstance(strs, six.string_types):
- for s in strs.splitlines():
- s = s.strip()
- # skip blank lines/comments
- if s and not s.startswith('#'):
- yield s
- else:
- for ss in strs:
- for s in yield_lines(ss):
- yield s
-
-
MODULE = re.compile(r"\w+(\.\w+)*$").match
EGG_NAME = re.compile(
r"""
@@ -2450,7 +2478,7 @@ class EntryPoint:
try:
return functools.reduce(getattr, self.attrs, module)
except AttributeError as exc:
- raise ImportError(str(exc))
+ raise ImportError(str(exc)) from exc
def require(self, env=None, installer=None):
if self.extras and not self.dist:
@@ -2536,15 +2564,6 @@ class EntryPoint:
return maps
-def _remove_md5_fragment(location):
- if not location:
- return ''
- parsed = urllib.parse.urlparse(location)
- if parsed[-1].startswith('md5='):
- return urllib.parse.urlunparse(parsed[:-1] + ('',))
- return location
-
-
def _version_from_file(lines):
"""
Given an iterable of lines from a Metadata file, return
@@ -2601,7 +2620,7 @@ class Distribution:
self.parsed_version,
self.precedence,
self.key,
- _remove_md5_fragment(self.location),
+ self.location,
self.py_version or '',
self.platform or '',
)
@@ -2679,14 +2698,14 @@ class Distribution:
def version(self):
try:
return self._version
- except AttributeError:
+ except AttributeError as e:
version = self._get_version()
if version is None:
path = self._get_metadata_path_for_display(self.PKG_INFO)
msg = (
"Missing 'Version:' header and/or {} file at path: {}"
).format(self.PKG_INFO, path)
- raise ValueError(msg, self)
+ raise ValueError(msg, self) from e
return version
@@ -2739,10 +2758,10 @@ class Distribution:
for ext in extras:
try:
deps.extend(dm[safe_extra(ext)])
- except KeyError:
+ except KeyError as e:
raise UnknownExtra(
"%s has no such extra feature %r" % (self, ext)
- )
+ ) from e
return deps
def _get_metadata_path_for_display(self, name):
@@ -2824,10 +2843,6 @@ class Distribution:
)
)
- if not hasattr(object, '__dir__'):
- # python 2.7 not supported
- del __dir__
-
@classmethod
def from_filename(cls, filename, metadata=None, **kw):
return cls.from_location(
@@ -2867,7 +2882,8 @@ class Distribution:
"""Return the EntryPoint object for `group`+`name`, or ``None``"""
return self.get_entry_map(group).get(name)
- def insert_on(self, path, loc=None, replace=False):
+ # FIXME: 'Distribution.insert_on' is too complex (13)
+ def insert_on(self, path, loc=None, replace=False): # noqa: C901
"""Ensure self.location is on path
If replace=False (default):
@@ -3037,12 +3053,12 @@ class DistInfoDistribution(Distribution):
if not req.marker or req.marker.evaluate({'extra': extra}):
yield req
- common = frozenset(reqs_for_extra(None))
+ common = types.MappingProxyType(dict.fromkeys(reqs_for_extra(None)))
dm[None].extend(common)
for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []:
s_extra = safe_extra(extra.strip())
- dm[s_extra] = list(frozenset(reqs_for_extra(extra)) - common)
+ dm[s_extra] = [r for r in reqs_for_extra(extra) if r not in common]
return dm
@@ -3067,40 +3083,23 @@ def issue_warning(*args, **kw):
warnings.warn(stacklevel=level + 1, *args, **kw)
-class RequirementParseError(ValueError):
- def __str__(self):
- return ' '.join(self.args)
-
-
def parse_requirements(strs):
- """Yield ``Requirement`` objects for each specification in `strs`
+ """
+ Yield ``Requirement`` objects for each specification in `strs`.
`strs` must be a string, or a (possibly-nested) iterable thereof.
"""
- # create a steppable iterator, so we can handle \-continuations
- lines = iter(yield_lines(strs))
+ return map(Requirement, join_continuation(map(drop_comment, yield_lines(strs))))
- for line in lines:
- # Drop comments -- a hash without a space may be in a URL.
- if ' #' in line:
- line = line[:line.find(' #')]
- # If there is a line continuation, drop it, and append the next line.
- if line.endswith('\\'):
- line = line[:-2].strip()
- try:
- line += next(lines)
- except StopIteration:
- return
- yield Requirement(line)
+
+class RequirementParseError(packaging.requirements.InvalidRequirement):
+ "Compatibility wrapper for InvalidRequirement"
class Requirement(packaging.requirements.Requirement):
def __init__(self, requirement_string):
"""DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!"""
- try:
- super(Requirement, self).__init__(requirement_string)
- except packaging.requirements.InvalidRequirement as e:
- raise RequirementParseError(str(e))
+ super(Requirement, self).__init__(requirement_string)
self.unsafe_name = self.name
project_name = safe_name(self.name)
self.project_name, self.key = project_name, project_name.lower()
@@ -3170,7 +3169,7 @@ def _find_adapter(registry, ob):
def ensure_directory(path):
"""Ensure that the parent directory of `path` exists"""
dirname = os.path.dirname(path)
- py31compat.makedirs(dirname, exist_ok=True)
+ os.makedirs(dirname, exist_ok=True)
def _bypass_ensure_directory(path):
@@ -3248,6 +3247,15 @@ def _initialize(g=globals()):
)
+class PkgResourcesDeprecationWarning(Warning):
+ """
+ Base class for warning about deprecations in ``pkg_resources``
+
+ This class is not derived from ``DeprecationWarning``, and as such is
+ visible by default.
+ """
+
+
@_call_aside
def _initialize_master_working_set():
"""
@@ -3286,11 +3294,3 @@ def _initialize_master_working_set():
# match order
list(map(working_set.add_entry, sys.path))
globals().update(locals())
-
-class PkgResourcesDeprecationWarning(Warning):
- """
- Base class for warning about deprecations in ``pkg_resources``
-
- This class is not derived from ``DeprecationWarning``, and as such is
- visible by default.
- """
diff --git a/src/pip/_vendor/pkg_resources/py31compat.py b/src/pip/_vendor/pkg_resources/py31compat.py
deleted file mode 100644
index a2d3007ce..000000000
--- a/src/pip/_vendor/pkg_resources/py31compat.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import os
-import errno
-import sys
-
-from pip._vendor import six
-
-
-def _makedirs_31(path, exist_ok=False):
- try:
- os.makedirs(path)
- except OSError as exc:
- if not exist_ok or exc.errno != errno.EEXIST:
- raise
-
-
-# rely on compatibility behavior until mode considerations
-# and exists_ok considerations are disentangled.
-# See https://github.com/pypa/setuptools/pull/1083#issuecomment-315168663
-needs_makedirs = (
- six.PY2 or
- (3, 4) <= sys.version_info < (3, 4, 1)
-)
-makedirs = _makedirs_31 if needs_makedirs else os.makedirs
diff --git a/src/pip/_vendor/platformdirs/LICENSE b/src/pip/_vendor/platformdirs/LICENSE
new file mode 100644
index 000000000..f35fed919
--- /dev/null
+++ b/src/pip/_vendor/platformdirs/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2010-202x The platformdirs developers
+
+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.
diff --git a/src/pip/_vendor/platformdirs/LICENSE.txt b/src/pip/_vendor/platformdirs/LICENSE.txt
deleted file mode 100644
index f0bbd69f0..000000000
--- a/src/pip/_vendor/platformdirs/LICENSE.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-# This is the MIT license
-
-Copyright (c) 2010 ActiveState Software Inc.
-
-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.
diff --git a/src/pip/_vendor/platformdirs/__init__.py b/src/pip/_vendor/platformdirs/__init__.py
index 81c567d57..82d907163 100644
--- a/src/pip/_vendor/platformdirs/__init__.py
+++ b/src/pip/_vendor/platformdirs/__init__.py
@@ -2,30 +2,43 @@
Utilities for determining application-specific dirs. See <https://github.com/platformdirs/platformdirs> for details and
usage.
"""
-import importlib
+from __future__ import annotations
+
import os
import sys
from pathlib import Path
-from typing import TYPE_CHECKING, Optional, Type, Union
-if TYPE_CHECKING:
- from typing_extensions import Literal # pragma: no cover
+if sys.version_info >= (3, 8): # pragma: no cover (py38+)
+ from typing import Literal
+else: # pragma: no cover (py38+)
+ from pip._vendor.typing_extensions import Literal
from .api import PlatformDirsABC
-from .version import __version__, __version_info__
+from .version import __version__
+from .version import __version_tuple__ as __version_info__
-def _set_platform_dir_class() -> Type[PlatformDirsABC]:
- if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system":
- module, name = "pip._vendor.platformdirs.android", "Android"
- elif sys.platform == "win32":
- module, name = "pip._vendor.platformdirs.windows", "Windows"
+def _set_platform_dir_class() -> type[PlatformDirsABC]:
+ if sys.platform == "win32":
+ from pip._vendor.platformdirs.windows import Windows as Result
elif sys.platform == "darwin":
- module, name = "pip._vendor.platformdirs.macos", "MacOS"
+ from pip._vendor.platformdirs.macos import MacOS as Result
else:
- module, name = "pip._vendor.platformdirs.unix", "Unix"
- result: Type[PlatformDirsABC] = getattr(importlib.import_module(module), name)
- return result
+ from pip._vendor.platformdirs.unix import Unix as Result
+
+ if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system":
+
+ if os.getenv("SHELL") or os.getenv("PREFIX"):
+ return Result
+
+ from pip._vendor.platformdirs.android import _android_folder
+
+ if _android_folder() is not None:
+ from pip._vendor.platformdirs.android import Android
+
+ return Android # return to avoid redefinition of result
+
+ return Result
PlatformDirs = _set_platform_dir_class() #: Currently active platform
@@ -33,9 +46,9 @@ AppDirs = PlatformDirs #: Backwards compatibility with appdirs
def user_data_dir(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
roaming: bool = False,
) -> str:
"""
@@ -49,9 +62,9 @@ def user_data_dir(
def site_data_dir(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
multipath: bool = False,
) -> str:
"""
@@ -65,9 +78,9 @@ def site_data_dir(
def user_config_dir(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
roaming: bool = False,
) -> str:
"""
@@ -81,9 +94,9 @@ def user_config_dir(
def site_config_dir(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
multipath: bool = False,
) -> str:
"""
@@ -97,9 +110,9 @@ def site_config_dir(
def user_cache_dir(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
opinion: bool = True,
) -> str:
"""
@@ -113,9 +126,9 @@ def user_cache_dir(
def user_state_dir(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
roaming: bool = False,
) -> str:
"""
@@ -129,9 +142,9 @@ def user_state_dir(
def user_log_dir(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
opinion: bool = True,
) -> str:
"""
@@ -152,9 +165,9 @@ def user_documents_dir() -> str:
def user_runtime_dir(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
opinion: bool = True,
) -> str:
"""
@@ -168,9 +181,9 @@ def user_runtime_dir(
def user_data_path(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
roaming: bool = False,
) -> Path:
"""
@@ -184,9 +197,9 @@ def user_data_path(
def site_data_path(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
multipath: bool = False,
) -> Path:
"""
@@ -200,9 +213,9 @@ def site_data_path(
def user_config_path(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
roaming: bool = False,
) -> Path:
"""
@@ -216,9 +229,9 @@ def user_config_path(
def site_config_path(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
multipath: bool = False,
) -> Path:
"""
@@ -232,9 +245,9 @@ def site_config_path(
def user_cache_path(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
opinion: bool = True,
) -> Path:
"""
@@ -248,9 +261,9 @@ def user_cache_path(
def user_state_path(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
roaming: bool = False,
) -> Path:
"""
@@ -264,9 +277,9 @@ def user_state_path(
def user_log_path(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
opinion: bool = True,
) -> Path:
"""
@@ -287,9 +300,9 @@ def user_documents_path() -> Path:
def user_runtime_path(
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
opinion: bool = True,
) -> Path:
"""
diff --git a/src/pip/_vendor/platformdirs/__main__.py b/src/pip/_vendor/platformdirs/__main__.py
index 2c3237270..9c54bfb43 100644
--- a/src/pip/_vendor/platformdirs/__main__.py
+++ b/src/pip/_vendor/platformdirs/__main__.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pip._vendor.platformdirs import PlatformDirs, __version__
PROPS = (
diff --git a/src/pip/_vendor/platformdirs/android.py b/src/pip/_vendor/platformdirs/android.py
index 75990457a..eda809351 100644
--- a/src/pip/_vendor/platformdirs/android.py
+++ b/src/pip/_vendor/platformdirs/android.py
@@ -1,7 +1,10 @@
+from __future__ import annotations
+
import os
import re
import sys
from functools import lru_cache
+from typing import cast
from .api import PlatformDirsABC
@@ -16,7 +19,7 @@ class Android(PlatformDirsABC):
@property
def user_data_dir(self) -> str:
""":return: data directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/files/<AppName>``"""
- return self._append_app_name_and_version(_android_folder(), "files")
+ return self._append_app_name_and_version(cast(str, _android_folder()), "files")
@property
def site_data_dir(self) -> str:
@@ -28,7 +31,7 @@ class Android(PlatformDirsABC):
"""
:return: config directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/shared_prefs/<AppName>``
"""
- return self._append_app_name_and_version(_android_folder(), "shared_prefs")
+ return self._append_app_name_and_version(cast(str, _android_folder()), "shared_prefs")
@property
def site_config_dir(self) -> str:
@@ -38,7 +41,7 @@ class Android(PlatformDirsABC):
@property
def user_cache_dir(self) -> str:
""":return: cache directory tied to the user, e.g. e.g. ``/data/user/<userid>/<packagename>/cache/<AppName>``"""
- return self._append_app_name_and_version(_android_folder(), "cache")
+ return self._append_app_name_and_version(cast(str, _android_folder()), "cache")
@property
def user_state_dir(self) -> str:
@@ -76,14 +79,14 @@ class Android(PlatformDirsABC):
@lru_cache(maxsize=1)
-def _android_folder() -> str:
- """:return: base folder for the Android OS"""
+def _android_folder() -> str | None:
+ """:return: base folder for the Android OS or None if cannot be found"""
try:
# First try to get path to android app via pyjnius
- from jnius import autoclass # noqa: SC200
+ from jnius import autoclass
- Context = autoclass("android.content.Context") # noqa: SC200
- result: str = Context.getFilesDir().getParentFile().getAbsolutePath()
+ Context = autoclass("android.content.Context") # noqa: N806
+ result: str | None = Context.getFilesDir().getParentFile().getAbsolutePath()
except Exception:
# if fails find an android folder looking path on the sys.path
pattern = re.compile(r"/data/(data|user/\d+)/(.+)/files")
@@ -92,7 +95,7 @@ def _android_folder() -> str:
result = path.split("/files")[0]
break
else:
- raise OSError("Cannot find path to android app folder")
+ result = None
return result
@@ -101,10 +104,10 @@ def _android_documents_folder() -> str:
""":return: documents folder for the Android OS"""
# Get directories with pyjnius
try:
- from jnius import autoclass # noqa: SC200
+ from jnius import autoclass
- Context = autoclass("android.content.Context") # noqa: SC200
- Environment = autoclass("android.os.Environment")
+ Context = autoclass("android.content.Context") # noqa: N806
+ Environment = autoclass("android.os.Environment") # noqa: N806
documents_dir: str = Context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath()
except Exception:
documents_dir = "/storage/emulated/0/Documents"
diff --git a/src/pip/_vendor/platformdirs/api.py b/src/pip/_vendor/platformdirs/api.py
index ebd96afeb..6f6e2c2c6 100644
--- a/src/pip/_vendor/platformdirs/api.py
+++ b/src/pip/_vendor/platformdirs/api.py
@@ -1,8 +1,9 @@
+from __future__ import annotations
+
import os
import sys
from abc import ABC, abstractmethod
from pathlib import Path
-from typing import Optional, Union
if sys.version_info >= (3, 8): # pragma: no branch
from typing import Literal # pragma: no cover
@@ -15,9 +16,9 @@ class PlatformDirsABC(ABC):
def __init__(
self,
- appname: Optional[str] = None,
- appauthor: Union[str, None, "Literal[False]"] = None,
- version: Optional[str] = None,
+ appname: str | None = None,
+ appauthor: str | None | Literal[False] = None,
+ version: str | None = None,
roaming: bool = False,
multipath: bool = False,
opinion: bool = True,
diff --git a/src/pip/_vendor/platformdirs/macos.py b/src/pip/_vendor/platformdirs/macos.py
index fffd1a7f5..a01337c77 100644
--- a/src/pip/_vendor/platformdirs/macos.py
+++ b/src/pip/_vendor/platformdirs/macos.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
from .api import PlatformDirsABC
diff --git a/src/pip/_vendor/platformdirs/unix.py b/src/pip/_vendor/platformdirs/unix.py
index 688b2799a..9aca5a030 100644
--- a/src/pip/_vendor/platformdirs/unix.py
+++ b/src/pip/_vendor/platformdirs/unix.py
@@ -1,8 +1,9 @@
+from __future__ import annotations
+
import os
import sys
from configparser import ConfigParser
from pathlib import Path
-from typing import Optional
from .api import PlatformDirsABC
@@ -106,9 +107,9 @@ class Unix(PlatformDirsABC):
@property
def user_log_dir(self) -> str:
"""
- :return: log directory tied to the user, same as `user_data_dir` if not opinionated else ``log`` in it
+ :return: log directory tied to the user, same as `user_state_dir` if not opinionated else ``log`` in it
"""
- path = self.user_cache_dir
+ path = self.user_state_dir
if self.opinion:
path = os.path.join(path, "log")
return path
@@ -154,7 +155,7 @@ class Unix(PlatformDirsABC):
return Path(directory)
-def _get_user_dirs_folder(key: str) -> Optional[str]:
+def _get_user_dirs_folder(key: str) -> str | None:
"""Return directory from user-dirs.dirs config file. See https://freedesktop.org/wiki/Software/xdg-user-dirs/"""
user_dirs_config_path = os.path.join(Unix().user_config_dir, "user-dirs.dirs")
if os.path.exists(user_dirs_config_path):
diff --git a/src/pip/_vendor/platformdirs/version.py b/src/pip/_vendor/platformdirs/version.py
index 3bdf97096..9f6eb98e8 100644
--- a/src/pip/_vendor/platformdirs/version.py
+++ b/src/pip/_vendor/platformdirs/version.py
@@ -1,4 +1,4 @@
-""" Version information """
-
-__version__ = "2.4.0"
-__version_info__ = (2, 4, 0)
+# file generated by setuptools_scm
+# don't change, don't track in version control
+__version__ = version = '2.6.2'
+__version_tuple__ = version_tuple = (2, 6, 2)
diff --git a/src/pip/_vendor/platformdirs/windows.py b/src/pip/_vendor/platformdirs/windows.py
index c75cf99e9..d5c27b341 100644
--- a/src/pip/_vendor/platformdirs/windows.py
+++ b/src/pip/_vendor/platformdirs/windows.py
@@ -1,7 +1,10 @@
+from __future__ import annotations
+
import ctypes
import os
+import sys
from functools import lru_cache
-from typing import Callable, Optional
+from typing import Callable
from .api import PlatformDirsABC
@@ -27,7 +30,7 @@ class Windows(PlatformDirsABC):
path = os.path.normpath(get_win_folder(const))
return self._append_parts(path)
- def _append_parts(self, path: str, *, opinion_value: Optional[str] = None) -> str:
+ def _append_parts(self, path: str, *, opinion_value: str | None = None) -> str:
params = []
if self.appname:
if self.appauthor is not False:
@@ -130,7 +133,8 @@ def get_win_folder_from_registry(csidl_name: str) -> str:
}.get(csidl_name)
if shell_folder_name is None:
raise ValueError(f"Unknown CSIDL name: {csidl_name}")
-
+ if sys.platform != "win32": # only needed for mypy type checker to know that this code runs only on Windows
+ raise NotImplementedError
import winreg
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
diff --git a/src/pip/_vendor/progress.pyi b/src/pip/_vendor/progress.pyi
deleted file mode 100644
index c92de832b..000000000
--- a/src/pip/_vendor/progress.pyi
+++ /dev/null
@@ -1 +0,0 @@
-from progress import * \ No newline at end of file
diff --git a/src/pip/_vendor/progress/LICENSE b/src/pip/_vendor/progress/LICENSE
deleted file mode 100644
index fb3ac14a6..000000000
--- a/src/pip/_vendor/progress/LICENSE
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright (c) 2012 Georgios Verigakis <verigak@gmail.com>
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/src/pip/_vendor/progress/__init__.py b/src/pip/_vendor/progress/__init__.py
deleted file mode 100644
index b434b300a..000000000
--- a/src/pip/_vendor/progress/__init__.py
+++ /dev/null
@@ -1,189 +0,0 @@
-# Copyright (c) 2012 Georgios Verigakis <verigak@gmail.com>
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-from __future__ import division, print_function
-
-from collections import deque
-from datetime import timedelta
-from math import ceil
-from sys import stderr
-try:
- from time import monotonic
-except ImportError:
- from time import time as monotonic
-
-
-__version__ = '1.6'
-
-HIDE_CURSOR = '\x1b[?25l'
-SHOW_CURSOR = '\x1b[?25h'
-
-
-class Infinite(object):
- file = stderr
- sma_window = 10 # Simple Moving Average window
- check_tty = True
- hide_cursor = True
-
- def __init__(self, message='', **kwargs):
- self.index = 0
- self.start_ts = monotonic()
- self.avg = 0
- self._avg_update_ts = self.start_ts
- self._ts = self.start_ts
- self._xput = deque(maxlen=self.sma_window)
- for key, val in kwargs.items():
- setattr(self, key, val)
-
- self._max_width = 0
- self._hidden_cursor = False
- self.message = message
-
- if self.file and self.is_tty():
- if self.hide_cursor:
- print(HIDE_CURSOR, end='', file=self.file)
- self._hidden_cursor = True
- self.writeln('')
-
- def __del__(self):
- if self._hidden_cursor:
- print(SHOW_CURSOR, end='', file=self.file)
-
- def __getitem__(self, key):
- if key.startswith('_'):
- return None
- return getattr(self, key, None)
-
- @property
- def elapsed(self):
- return int(monotonic() - self.start_ts)
-
- @property
- def elapsed_td(self):
- return timedelta(seconds=self.elapsed)
-
- def update_avg(self, n, dt):
- if n > 0:
- xput_len = len(self._xput)
- self._xput.append(dt / n)
- now = monotonic()
- # update when we're still filling _xput, then after every second
- if (xput_len < self.sma_window or
- now - self._avg_update_ts > 1):
- self.avg = sum(self._xput) / len(self._xput)
- self._avg_update_ts = now
-
- def update(self):
- pass
-
- def start(self):
- pass
-
- def writeln(self, line):
- if self.file and self.is_tty():
- width = len(line)
- if width < self._max_width:
- # Add padding to cover previous contents
- line += ' ' * (self._max_width - width)
- else:
- self._max_width = width
- print('\r' + line, end='', file=self.file)
- self.file.flush()
-
- def finish(self):
- if self.file and self.is_tty():
- print(file=self.file)
- if self._hidden_cursor:
- print(SHOW_CURSOR, end='', file=self.file)
- self._hidden_cursor = False
-
- def is_tty(self):
- try:
- return self.file.isatty() if self.check_tty else True
- except AttributeError:
- msg = "%s has no attribute 'isatty'. Try setting check_tty=False." % self
- raise AttributeError(msg)
-
- def next(self, n=1):
- now = monotonic()
- dt = now - self._ts
- self.update_avg(n, dt)
- self._ts = now
- self.index = self.index + n
- self.update()
-
- def iter(self, it):
- self.iter_value = None
- with self:
- for x in it:
- self.iter_value = x
- yield x
- self.next()
- del self.iter_value
-
- def __enter__(self):
- self.start()
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- self.finish()
-
-
-class Progress(Infinite):
- def __init__(self, *args, **kwargs):
- super(Progress, self).__init__(*args, **kwargs)
- self.max = kwargs.get('max', 100)
-
- @property
- def eta(self):
- return int(ceil(self.avg * self.remaining))
-
- @property
- def eta_td(self):
- return timedelta(seconds=self.eta)
-
- @property
- def percent(self):
- return self.progress * 100
-
- @property
- def progress(self):
- if self.max == 0:
- return 0
- return min(1, self.index / self.max)
-
- @property
- def remaining(self):
- return max(self.max - self.index, 0)
-
- def start(self):
- self.update()
-
- def goto(self, index):
- incr = index - self.index
- self.next(incr)
-
- def iter(self, it):
- try:
- self.max = len(it)
- except TypeError:
- pass
-
- self.iter_value = None
- with self:
- for x in it:
- self.iter_value = x
- yield x
- self.next()
- del self.iter_value
diff --git a/src/pip/_vendor/progress/bar.py b/src/pip/_vendor/progress/bar.py
deleted file mode 100644
index df4e8b61f..000000000
--- a/src/pip/_vendor/progress/bar.py
+++ /dev/null
@@ -1,93 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2012 Georgios Verigakis <verigak@gmail.com>
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-from __future__ import unicode_literals
-
-import sys
-
-from . import Progress
-from .colors import color
-
-
-class Bar(Progress):
- width = 32
- suffix = '%(index)d/%(max)d'
- bar_prefix = ' |'
- bar_suffix = '| '
- empty_fill = ' '
- fill = '#'
- color = None
-
- def update(self):
- filled_length = int(self.width * self.progress)
- empty_length = self.width - filled_length
-
- message = self.message % self
- bar = color(self.fill * filled_length, fg=self.color)
- empty = self.empty_fill * empty_length
- suffix = self.suffix % self
- line = ''.join([message, self.bar_prefix, bar, empty, self.bar_suffix,
- suffix])
- self.writeln(line)
-
-
-class ChargingBar(Bar):
- suffix = '%(percent)d%%'
- bar_prefix = ' '
- bar_suffix = ' '
- empty_fill = '∙'
- fill = '█'
-
-
-class FillingSquaresBar(ChargingBar):
- empty_fill = '▢'
- fill = '▣'
-
-
-class FillingCirclesBar(ChargingBar):
- empty_fill = '◯'
- fill = '◉'
-
-
-class IncrementalBar(Bar):
- if sys.platform.startswith('win'):
- phases = (u' ', u'▌', u'█')
- else:
- phases = (' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█')
-
- def update(self):
- nphases = len(self.phases)
- filled_len = self.width * self.progress
- nfull = int(filled_len) # Number of full chars
- phase = int((filled_len - nfull) * nphases) # Phase of last char
- nempty = self.width - nfull # Number of empty chars
-
- message = self.message % self
- bar = color(self.phases[-1] * nfull, fg=self.color)
- current = self.phases[phase] if phase > 0 else ''
- empty = self.empty_fill * max(0, nempty - len(current))
- suffix = self.suffix % self
- line = ''.join([message, self.bar_prefix, bar, current, empty,
- self.bar_suffix, suffix])
- self.writeln(line)
-
-
-class PixelBar(IncrementalBar):
- phases = ('⡀', '⡄', '⡆', '⡇', '⣇', '⣧', '⣷', '⣿')
-
-
-class ShadyBar(IncrementalBar):
- phases = (' ', '░', '▒', '▓', '█')
diff --git a/src/pip/_vendor/progress/colors.py b/src/pip/_vendor/progress/colors.py
deleted file mode 100644
index 4e770f868..000000000
--- a/src/pip/_vendor/progress/colors.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2020 Georgios Verigakis <verigak@gmail.com>
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-from functools import partial
-
-
-COLORS = ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan',
- 'white')
-STYLES = ('bold', 'faint', 'italic', 'underline', 'blink', 'blink2',
- 'negative', 'concealed', 'crossed')
-
-
-def color(s, fg=None, bg=None, style=None):
- sgr = []
-
- if fg:
- if fg in COLORS:
- sgr.append(str(30 + COLORS.index(fg)))
- elif isinstance(fg, int) and 0 <= fg <= 255:
- sgr.append('38;5;%d' % int(fg))
- else:
- raise Exception('Invalid color "%s"' % fg)
-
- if bg:
- if bg in COLORS:
- sgr.append(str(40 + COLORS.index(bg)))
- elif isinstance(bg, int) and 0 <= bg <= 255:
- sgr.append('48;5;%d' % bg)
- else:
- raise Exception('Invalid color "%s"' % bg)
-
- if style:
- for st in style.split('+'):
- if st in STYLES:
- sgr.append(str(1 + STYLES.index(st)))
- else:
- raise Exception('Invalid style "%s"' % st)
-
- if sgr:
- prefix = '\x1b[' + ';'.join(sgr) + 'm'
- suffix = '\x1b[0m'
- return prefix + s + suffix
- else:
- return s
-
-
-# Foreground shortcuts
-black = partial(color, fg='black')
-red = partial(color, fg='red')
-green = partial(color, fg='green')
-yellow = partial(color, fg='yellow')
-blue = partial(color, fg='blue')
-magenta = partial(color, fg='magenta')
-cyan = partial(color, fg='cyan')
-white = partial(color, fg='white')
-
-# Style shortcuts
-bold = partial(color, style='bold')
-faint = partial(color, style='faint')
-italic = partial(color, style='italic')
-underline = partial(color, style='underline')
-blink = partial(color, style='blink')
-blink2 = partial(color, style='blink2')
-negative = partial(color, style='negative')
-concealed = partial(color, style='concealed')
-crossed = partial(color, style='crossed')
diff --git a/src/pip/_vendor/progress/counter.py b/src/pip/_vendor/progress/counter.py
deleted file mode 100644
index d0fbe7ef3..000000000
--- a/src/pip/_vendor/progress/counter.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2012 Georgios Verigakis <verigak@gmail.com>
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-from __future__ import unicode_literals
-from . import Infinite, Progress
-
-
-class Counter(Infinite):
- def update(self):
- message = self.message % self
- line = ''.join([message, str(self.index)])
- self.writeln(line)
-
-
-class Countdown(Progress):
- def update(self):
- message = self.message % self
- line = ''.join([message, str(self.remaining)])
- self.writeln(line)
-
-
-class Stack(Progress):
- phases = (' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█')
-
- def update(self):
- nphases = len(self.phases)
- i = min(nphases - 1, int(self.progress * nphases))
- message = self.message % self
- line = ''.join([message, self.phases[i]])
- self.writeln(line)
-
-
-class Pie(Stack):
- phases = ('○', '◔', '◑', '◕', '●')
diff --git a/src/pip/_vendor/progress/spinner.py b/src/pip/_vendor/progress/spinner.py
deleted file mode 100644
index d593a203e..000000000
--- a/src/pip/_vendor/progress/spinner.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2012 Georgios Verigakis <verigak@gmail.com>
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-from __future__ import unicode_literals
-from . import Infinite
-
-
-class Spinner(Infinite):
- phases = ('-', '\\', '|', '/')
- hide_cursor = True
-
- def update(self):
- i = self.index % len(self.phases)
- message = self.message % self
- line = ''.join([message, self.phases[i]])
- self.writeln(line)
-
-
-class PieSpinner(Spinner):
- phases = ['◷', '◶', '◵', '◴']
-
-
-class MoonSpinner(Spinner):
- phases = ['◑', '◒', '◐', '◓']
-
-
-class LineSpinner(Spinner):
- phases = ['⎺', '⎻', '⎼', '⎽', '⎼', '⎻']
-
-
-class PixelSpinner(Spinner):
- phases = ['⣾', '⣷', '⣯', '⣟', '⡿', '⢿', '⣻', '⣽']
diff --git a/src/pip/_vendor/pygments.pyi b/src/pip/_vendor/pygments.pyi
new file mode 100644
index 000000000..566eaff36
--- /dev/null
+++ b/src/pip/_vendor/pygments.pyi
@@ -0,0 +1 @@
+from pygments import * \ No newline at end of file
diff --git a/src/pip/_vendor/pygments/LICENSE b/src/pip/_vendor/pygments/LICENSE
new file mode 100644
index 000000000..446a1a805
--- /dev/null
+++ b/src/pip/_vendor/pygments/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2006-2022 by the respective authors (see AUTHORS file).
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/pip/_vendor/pygments/__init__.py b/src/pip/_vendor/pygments/__init__.py
new file mode 100644
index 000000000..7185e5376
--- /dev/null
+++ b/src/pip/_vendor/pygments/__init__.py
@@ -0,0 +1,82 @@
+"""
+ Pygments
+ ~~~~~~~~
+
+ Pygments is a syntax highlighting package written in Python.
+
+ It is a generic syntax highlighter for general use in all kinds of software
+ such as forum systems, wikis or other applications that need to prettify
+ source code. Highlights are:
+
+ * a wide range of common languages and markup formats is supported
+ * special attention is paid to details, increasing quality by a fair amount
+ * support for new languages and formats are added easily
+ * a number of output formats, presently HTML, LaTeX, RTF, SVG, all image
+ formats that PIL supports, and ANSI sequences
+ * it is usable as a command-line tool and as a library
+ * ... and it highlights even Brainfuck!
+
+ The `Pygments master branch`_ is installable with ``easy_install Pygments==dev``.
+
+ .. _Pygments master branch:
+ https://github.com/pygments/pygments/archive/master.zip#egg=Pygments-dev
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+from io import StringIO, BytesIO
+
+__version__ = '2.13.0'
+__docformat__ = 'restructuredtext'
+
+__all__ = ['lex', 'format', 'highlight']
+
+
+def lex(code, lexer):
+ """
+ Lex ``code`` with ``lexer`` and return an iterable of tokens.
+ """
+ try:
+ return lexer.get_tokens(code)
+ except TypeError:
+ # Heuristic to catch a common mistake.
+ from pip._vendor.pygments.lexer import RegexLexer
+ if isinstance(lexer, type) and issubclass(lexer, RegexLexer):
+ raise TypeError('lex() argument must be a lexer instance, '
+ 'not a class')
+ raise
+
+
+def format(tokens, formatter, outfile=None): # pylint: disable=redefined-builtin
+ """
+ Format a tokenlist ``tokens`` with the formatter ``formatter``.
+
+ If ``outfile`` is given and a valid file object (an object
+ with a ``write`` method), the result will be written to it, otherwise
+ it is returned as a string.
+ """
+ try:
+ if not outfile:
+ realoutfile = getattr(formatter, 'encoding', None) and BytesIO() or StringIO()
+ formatter.format(tokens, realoutfile)
+ return realoutfile.getvalue()
+ else:
+ formatter.format(tokens, outfile)
+ except TypeError:
+ # Heuristic to catch a common mistake.
+ from pip._vendor.pygments.formatter import Formatter
+ if isinstance(formatter, type) and issubclass(formatter, Formatter):
+ raise TypeError('format() argument must be a formatter instance, '
+ 'not a class')
+ raise
+
+
+def highlight(code, lexer, formatter, outfile=None):
+ """
+ Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
+
+ If ``outfile`` is given and a valid file object (an object
+ with a ``write`` method), the result will be written to it, otherwise
+ it is returned as a string.
+ """
+ return format(lex(code, lexer), formatter, outfile)
diff --git a/src/pip/_vendor/pygments/__main__.py b/src/pip/_vendor/pygments/__main__.py
new file mode 100644
index 000000000..90cafd934
--- /dev/null
+++ b/src/pip/_vendor/pygments/__main__.py
@@ -0,0 +1,17 @@
+"""
+ pygments.__main__
+ ~~~~~~~~~~~~~~~~~
+
+ Main entry point for ``python -m pygments``.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import sys
+from pip._vendor.pygments.cmdline import main
+
+try:
+ sys.exit(main(sys.argv))
+except KeyboardInterrupt:
+ sys.exit(1)
diff --git a/src/pip/_vendor/pygments/cmdline.py b/src/pip/_vendor/pygments/cmdline.py
new file mode 100644
index 000000000..de73b06b4
--- /dev/null
+++ b/src/pip/_vendor/pygments/cmdline.py
@@ -0,0 +1,668 @@
+"""
+ pygments.cmdline
+ ~~~~~~~~~~~~~~~~
+
+ Command line interface.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import os
+import sys
+import shutil
+import argparse
+from textwrap import dedent
+
+from pip._vendor.pygments import __version__, highlight
+from pip._vendor.pygments.util import ClassNotFound, OptionError, docstring_headline, \
+ guess_decode, guess_decode_from_terminal, terminal_encoding, \
+ UnclosingTextIOWrapper
+from pip._vendor.pygments.lexers import get_all_lexers, get_lexer_by_name, guess_lexer, \
+ load_lexer_from_file, get_lexer_for_filename, find_lexer_class_for_filename
+from pip._vendor.pygments.lexers.special import TextLexer
+from pip._vendor.pygments.formatters.latex import LatexEmbeddedLexer, LatexFormatter
+from pip._vendor.pygments.formatters import get_all_formatters, get_formatter_by_name, \
+ load_formatter_from_file, get_formatter_for_filename, find_formatter_class
+from pip._vendor.pygments.formatters.terminal import TerminalFormatter
+from pip._vendor.pygments.formatters.terminal256 import Terminal256Formatter, TerminalTrueColorFormatter
+from pip._vendor.pygments.filters import get_all_filters, find_filter_class
+from pip._vendor.pygments.styles import get_all_styles, get_style_by_name
+
+
+def _parse_options(o_strs):
+ opts = {}
+ if not o_strs:
+ return opts
+ for o_str in o_strs:
+ if not o_str.strip():
+ continue
+ o_args = o_str.split(',')
+ for o_arg in o_args:
+ o_arg = o_arg.strip()
+ try:
+ o_key, o_val = o_arg.split('=', 1)
+ o_key = o_key.strip()
+ o_val = o_val.strip()
+ except ValueError:
+ opts[o_arg] = True
+ else:
+ opts[o_key] = o_val
+ return opts
+
+
+def _parse_filters(f_strs):
+ filters = []
+ if not f_strs:
+ return filters
+ for f_str in f_strs:
+ if ':' in f_str:
+ fname, fopts = f_str.split(':', 1)
+ filters.append((fname, _parse_options([fopts])))
+ else:
+ filters.append((f_str, {}))
+ return filters
+
+
+def _print_help(what, name):
+ try:
+ if what == 'lexer':
+ cls = get_lexer_by_name(name)
+ print("Help on the %s lexer:" % cls.name)
+ print(dedent(cls.__doc__))
+ elif what == 'formatter':
+ cls = find_formatter_class(name)
+ print("Help on the %s formatter:" % cls.name)
+ print(dedent(cls.__doc__))
+ elif what == 'filter':
+ cls = find_filter_class(name)
+ print("Help on the %s filter:" % name)
+ print(dedent(cls.__doc__))
+ return 0
+ except (AttributeError, ValueError):
+ print("%s not found!" % what, file=sys.stderr)
+ return 1
+
+
+def _print_list(what):
+ if what == 'lexer':
+ print()
+ print("Lexers:")
+ print("~~~~~~~")
+
+ info = []
+ for fullname, names, exts, _ in get_all_lexers():
+ tup = (', '.join(names)+':', fullname,
+ exts and '(filenames ' + ', '.join(exts) + ')' or '')
+ info.append(tup)
+ info.sort()
+ for i in info:
+ print(('* %s\n %s %s') % i)
+
+ elif what == 'formatter':
+ print()
+ print("Formatters:")
+ print("~~~~~~~~~~~")
+
+ info = []
+ for cls in get_all_formatters():
+ doc = docstring_headline(cls)
+ tup = (', '.join(cls.aliases) + ':', doc, cls.filenames and
+ '(filenames ' + ', '.join(cls.filenames) + ')' or '')
+ info.append(tup)
+ info.sort()
+ for i in info:
+ print(('* %s\n %s %s') % i)
+
+ elif what == 'filter':
+ print()
+ print("Filters:")
+ print("~~~~~~~~")
+
+ for name in get_all_filters():
+ cls = find_filter_class(name)
+ print("* " + name + ':')
+ print(" %s" % docstring_headline(cls))
+
+ elif what == 'style':
+ print()
+ print("Styles:")
+ print("~~~~~~~")
+
+ for name in get_all_styles():
+ cls = get_style_by_name(name)
+ print("* " + name + ':')
+ print(" %s" % docstring_headline(cls))
+
+
+def _print_list_as_json(requested_items):
+ import json
+ result = {}
+ if 'lexer' in requested_items:
+ info = {}
+ for fullname, names, filenames, mimetypes in get_all_lexers():
+ info[fullname] = {
+ 'aliases': names,
+ 'filenames': filenames,
+ 'mimetypes': mimetypes
+ }
+ result['lexers'] = info
+
+ if 'formatter' in requested_items:
+ info = {}
+ for cls in get_all_formatters():
+ doc = docstring_headline(cls)
+ info[cls.name] = {
+ 'aliases': cls.aliases,
+ 'filenames': cls.filenames,
+ 'doc': doc
+ }
+ result['formatters'] = info
+
+ if 'filter' in requested_items:
+ info = {}
+ for name in get_all_filters():
+ cls = find_filter_class(name)
+ info[name] = {
+ 'doc': docstring_headline(cls)
+ }
+ result['filters'] = info
+
+ if 'style' in requested_items:
+ info = {}
+ for name in get_all_styles():
+ cls = get_style_by_name(name)
+ info[name] = {
+ 'doc': docstring_headline(cls)
+ }
+ result['styles'] = info
+
+ json.dump(result, sys.stdout)
+
+def main_inner(parser, argns):
+ if argns.help:
+ parser.print_help()
+ return 0
+
+ if argns.V:
+ print('Pygments version %s, (c) 2006-2022 by Georg Brandl, Matthäus '
+ 'Chajdas and contributors.' % __version__)
+ return 0
+
+ def is_only_option(opt):
+ return not any(v for (k, v) in vars(argns).items() if k != opt)
+
+ # handle ``pygmentize -L``
+ if argns.L is not None:
+ arg_set = set()
+ for k, v in vars(argns).items():
+ if v:
+ arg_set.add(k)
+
+ arg_set.discard('L')
+ arg_set.discard('json')
+
+ if arg_set:
+ parser.print_help(sys.stderr)
+ return 2
+
+ # print version
+ if not argns.json:
+ main(['', '-V'])
+ allowed_types = {'lexer', 'formatter', 'filter', 'style'}
+ largs = [arg.rstrip('s') for arg in argns.L]
+ if any(arg not in allowed_types for arg in largs):
+ parser.print_help(sys.stderr)
+ return 0
+ if not largs:
+ largs = allowed_types
+ if not argns.json:
+ for arg in largs:
+ _print_list(arg)
+ else:
+ _print_list_as_json(largs)
+ return 0
+
+ # handle ``pygmentize -H``
+ if argns.H:
+ if not is_only_option('H'):
+ parser.print_help(sys.stderr)
+ return 2
+ what, name = argns.H
+ if what not in ('lexer', 'formatter', 'filter'):
+ parser.print_help(sys.stderr)
+ return 2
+ return _print_help(what, name)
+
+ # parse -O options
+ parsed_opts = _parse_options(argns.O or [])
+
+ # parse -P options
+ for p_opt in argns.P or []:
+ try:
+ name, value = p_opt.split('=', 1)
+ except ValueError:
+ parsed_opts[p_opt] = True
+ else:
+ parsed_opts[name] = value
+
+ # encodings
+ inencoding = parsed_opts.get('inencoding', parsed_opts.get('encoding'))
+ outencoding = parsed_opts.get('outencoding', parsed_opts.get('encoding'))
+
+ # handle ``pygmentize -N``
+ if argns.N:
+ lexer = find_lexer_class_for_filename(argns.N)
+ if lexer is None:
+ lexer = TextLexer
+
+ print(lexer.aliases[0])
+ return 0
+
+ # handle ``pygmentize -C``
+ if argns.C:
+ inp = sys.stdin.buffer.read()
+ try:
+ lexer = guess_lexer(inp, inencoding=inencoding)
+ except ClassNotFound:
+ lexer = TextLexer
+
+ print(lexer.aliases[0])
+ return 0
+
+ # handle ``pygmentize -S``
+ S_opt = argns.S
+ a_opt = argns.a
+ if S_opt is not None:
+ f_opt = argns.f
+ if not f_opt:
+ parser.print_help(sys.stderr)
+ return 2
+ if argns.l or argns.INPUTFILE:
+ parser.print_help(sys.stderr)
+ return 2
+
+ try:
+ parsed_opts['style'] = S_opt
+ fmter = get_formatter_by_name(f_opt, **parsed_opts)
+ except ClassNotFound as err:
+ print(err, file=sys.stderr)
+ return 1
+
+ print(fmter.get_style_defs(a_opt or ''))
+ return 0
+
+ # if no -S is given, -a is not allowed
+ if argns.a is not None:
+ parser.print_help(sys.stderr)
+ return 2
+
+ # parse -F options
+ F_opts = _parse_filters(argns.F or [])
+
+ # -x: allow custom (eXternal) lexers and formatters
+ allow_custom_lexer_formatter = bool(argns.x)
+
+ # select lexer
+ lexer = None
+
+ # given by name?
+ lexername = argns.l
+ if lexername:
+ # custom lexer, located relative to user's cwd
+ if allow_custom_lexer_formatter and '.py' in lexername:
+ try:
+ filename = None
+ name = None
+ if ':' in lexername:
+ filename, name = lexername.rsplit(':', 1)
+
+ if '.py' in name:
+ # This can happen on Windows: If the lexername is
+ # C:\lexer.py -- return to normal load path in that case
+ name = None
+
+ if filename and name:
+ lexer = load_lexer_from_file(filename, name,
+ **parsed_opts)
+ else:
+ lexer = load_lexer_from_file(lexername, **parsed_opts)
+ except ClassNotFound as err:
+ print('Error:', err, file=sys.stderr)
+ return 1
+ else:
+ try:
+ lexer = get_lexer_by_name(lexername, **parsed_opts)
+ except (OptionError, ClassNotFound) as err:
+ print('Error:', err, file=sys.stderr)
+ return 1
+
+ # read input code
+ code = None
+
+ if argns.INPUTFILE:
+ if argns.s:
+ print('Error: -s option not usable when input file specified',
+ file=sys.stderr)
+ return 2
+
+ infn = argns.INPUTFILE
+ try:
+ with open(infn, 'rb') as infp:
+ code = infp.read()
+ except Exception as err:
+ print('Error: cannot read infile:', err, file=sys.stderr)
+ return 1
+ if not inencoding:
+ code, inencoding = guess_decode(code)
+
+ # do we have to guess the lexer?
+ if not lexer:
+ try:
+ lexer = get_lexer_for_filename(infn, code, **parsed_opts)
+ except ClassNotFound as err:
+ if argns.g:
+ try:
+ lexer = guess_lexer(code, **parsed_opts)
+ except ClassNotFound:
+ lexer = TextLexer(**parsed_opts)
+ else:
+ print('Error:', err, file=sys.stderr)
+ return 1
+ except OptionError as err:
+ print('Error:', err, file=sys.stderr)
+ return 1
+
+ elif not argns.s: # treat stdin as full file (-s support is later)
+ # read code from terminal, always in binary mode since we want to
+ # decode ourselves and be tolerant with it
+ code = sys.stdin.buffer.read() # use .buffer to get a binary stream
+ if not inencoding:
+ code, inencoding = guess_decode_from_terminal(code, sys.stdin)
+ # else the lexer will do the decoding
+ if not lexer:
+ try:
+ lexer = guess_lexer(code, **parsed_opts)
+ except ClassNotFound:
+ lexer = TextLexer(**parsed_opts)
+
+ else: # -s option needs a lexer with -l
+ if not lexer:
+ print('Error: when using -s a lexer has to be selected with -l',
+ file=sys.stderr)
+ return 2
+
+ # process filters
+ for fname, fopts in F_opts:
+ try:
+ lexer.add_filter(fname, **fopts)
+ except ClassNotFound as err:
+ print('Error:', err, file=sys.stderr)
+ return 1
+
+ # select formatter
+ outfn = argns.o
+ fmter = argns.f
+ if fmter:
+ # custom formatter, located relative to user's cwd
+ if allow_custom_lexer_formatter and '.py' in fmter:
+ try:
+ filename = None
+ name = None
+ if ':' in fmter:
+ # Same logic as above for custom lexer
+ filename, name = fmter.rsplit(':', 1)
+
+ if '.py' in name:
+ name = None
+
+ if filename and name:
+ fmter = load_formatter_from_file(filename, name,
+ **parsed_opts)
+ else:
+ fmter = load_formatter_from_file(fmter, **parsed_opts)
+ except ClassNotFound as err:
+ print('Error:', err, file=sys.stderr)
+ return 1
+ else:
+ try:
+ fmter = get_formatter_by_name(fmter, **parsed_opts)
+ except (OptionError, ClassNotFound) as err:
+ print('Error:', err, file=sys.stderr)
+ return 1
+
+ if outfn:
+ if not fmter:
+ try:
+ fmter = get_formatter_for_filename(outfn, **parsed_opts)
+ except (OptionError, ClassNotFound) as err:
+ print('Error:', err, file=sys.stderr)
+ return 1
+ try:
+ outfile = open(outfn, 'wb')
+ except Exception as err:
+ print('Error: cannot open outfile:', err, file=sys.stderr)
+ return 1
+ else:
+ if not fmter:
+ if os.environ.get('COLORTERM','') in ('truecolor', '24bit'):
+ fmter = TerminalTrueColorFormatter(**parsed_opts)
+ elif '256' in os.environ.get('TERM', ''):
+ fmter = Terminal256Formatter(**parsed_opts)
+ else:
+ fmter = TerminalFormatter(**parsed_opts)
+ outfile = sys.stdout.buffer
+
+ # determine output encoding if not explicitly selected
+ if not outencoding:
+ if outfn:
+ # output file? use lexer encoding for now (can still be None)
+ fmter.encoding = inencoding
+ else:
+ # else use terminal encoding
+ fmter.encoding = terminal_encoding(sys.stdout)
+
+ # provide coloring under Windows, if possible
+ if not outfn and sys.platform in ('win32', 'cygwin') and \
+ fmter.name in ('Terminal', 'Terminal256'): # pragma: no cover
+ # unfortunately colorama doesn't support binary streams on Py3
+ outfile = UnclosingTextIOWrapper(outfile, encoding=fmter.encoding)
+ fmter.encoding = None
+ try:
+ import pip._vendor.colorama.initialise as colorama_initialise
+ except ImportError:
+ pass
+ else:
+ outfile = colorama_initialise.wrap_stream(
+ outfile, convert=None, strip=None, autoreset=False, wrap=True)
+
+ # When using the LaTeX formatter and the option `escapeinside` is
+ # specified, we need a special lexer which collects escaped text
+ # before running the chosen language lexer.
+ escapeinside = parsed_opts.get('escapeinside', '')
+ if len(escapeinside) == 2 and isinstance(fmter, LatexFormatter):
+ left = escapeinside[0]
+ right = escapeinside[1]
+ lexer = LatexEmbeddedLexer(left, right, lexer)
+
+ # ... and do it!
+ if not argns.s:
+ # process whole input as per normal...
+ try:
+ highlight(code, lexer, fmter, outfile)
+ finally:
+ if outfn:
+ outfile.close()
+ return 0
+ else:
+ # line by line processing of stdin (eg: for 'tail -f')...
+ try:
+ while 1:
+ line = sys.stdin.buffer.readline()
+ if not line:
+ break
+ if not inencoding:
+ line = guess_decode_from_terminal(line, sys.stdin)[0]
+ highlight(line, lexer, fmter, outfile)
+ if hasattr(outfile, 'flush'):
+ outfile.flush()
+ return 0
+ except KeyboardInterrupt: # pragma: no cover
+ return 0
+ finally:
+ if outfn:
+ outfile.close()
+
+
+class HelpFormatter(argparse.HelpFormatter):
+ def __init__(self, prog, indent_increment=2, max_help_position=16, width=None):
+ if width is None:
+ try:
+ width = shutil.get_terminal_size().columns - 2
+ except Exception:
+ pass
+ argparse.HelpFormatter.__init__(self, prog, indent_increment,
+ max_help_position, width)
+
+
+def main(args=sys.argv):
+ """
+ Main command line entry point.
+ """
+ desc = "Highlight an input file and write the result to an output file."
+ parser = argparse.ArgumentParser(description=desc, add_help=False,
+ formatter_class=HelpFormatter)
+
+ operation = parser.add_argument_group('Main operation')
+ lexersel = operation.add_mutually_exclusive_group()
+ lexersel.add_argument(
+ '-l', metavar='LEXER',
+ help='Specify the lexer to use. (Query names with -L.) If not '
+ 'given and -g is not present, the lexer is guessed from the filename.')
+ lexersel.add_argument(
+ '-g', action='store_true',
+ help='Guess the lexer from the file contents, or pass through '
+ 'as plain text if nothing can be guessed.')
+ operation.add_argument(
+ '-F', metavar='FILTER[:options]', action='append',
+ help='Add a filter to the token stream. (Query names with -L.) '
+ 'Filter options are given after a colon if necessary.')
+ operation.add_argument(
+ '-f', metavar='FORMATTER',
+ help='Specify the formatter to use. (Query names with -L.) '
+ 'If not given, the formatter is guessed from the output filename, '
+ 'and defaults to the terminal formatter if the output is to the '
+ 'terminal or an unknown file extension.')
+ operation.add_argument(
+ '-O', metavar='OPTION=value[,OPTION=value,...]', action='append',
+ help='Give options to the lexer and formatter as a comma-separated '
+ 'list of key-value pairs. '
+ 'Example: `-O bg=light,python=cool`.')
+ operation.add_argument(
+ '-P', metavar='OPTION=value', action='append',
+ help='Give a single option to the lexer and formatter - with this '
+ 'you can pass options whose value contains commas and equal signs. '
+ 'Example: `-P "heading=Pygments, the Python highlighter"`.')
+ operation.add_argument(
+ '-o', metavar='OUTPUTFILE',
+ help='Where to write the output. Defaults to standard output.')
+
+ operation.add_argument(
+ 'INPUTFILE', nargs='?',
+ help='Where to read the input. Defaults to standard input.')
+
+ flags = parser.add_argument_group('Operation flags')
+ flags.add_argument(
+ '-v', action='store_true',
+ help='Print a detailed traceback on unhandled exceptions, which '
+ 'is useful for debugging and bug reports.')
+ flags.add_argument(
+ '-s', action='store_true',
+ help='Process lines one at a time until EOF, rather than waiting to '
+ 'process the entire file. This only works for stdin, only for lexers '
+ 'with no line-spanning constructs, and is intended for streaming '
+ 'input such as you get from `tail -f`. '
+ 'Example usage: `tail -f sql.log | pygmentize -s -l sql`.')
+ flags.add_argument(
+ '-x', action='store_true',
+ help='Allow custom lexers and formatters to be loaded from a .py file '
+ 'relative to the current working directory. For example, '
+ '`-l ./customlexer.py -x`. By default, this option expects a file '
+ 'with a class named CustomLexer or CustomFormatter; you can also '
+ 'specify your own class name with a colon (`-l ./lexer.py:MyLexer`). '
+ 'Users should be very careful not to use this option with untrusted '
+ 'files, because it will import and run them.')
+ flags.add_argument('--json', help='Output as JSON. This can '
+ 'be only used in conjunction with -L.',
+ default=False,
+ action='store_true')
+
+ special_modes_group = parser.add_argument_group(
+ 'Special modes - do not do any highlighting')
+ special_modes = special_modes_group.add_mutually_exclusive_group()
+ special_modes.add_argument(
+ '-S', metavar='STYLE -f formatter',
+ help='Print style definitions for STYLE for a formatter '
+ 'given with -f. The argument given by -a is formatter '
+ 'dependent.')
+ special_modes.add_argument(
+ '-L', nargs='*', metavar='WHAT',
+ help='List lexers, formatters, styles or filters -- '
+ 'give additional arguments for the thing(s) you want to list '
+ '(e.g. "styles"), or omit them to list everything.')
+ special_modes.add_argument(
+ '-N', metavar='FILENAME',
+ help='Guess and print out a lexer name based solely on the given '
+ 'filename. Does not take input or highlight anything. If no specific '
+ 'lexer can be determined, "text" is printed.')
+ special_modes.add_argument(
+ '-C', action='store_true',
+ help='Like -N, but print out a lexer name based solely on '
+ 'a given content from standard input.')
+ special_modes.add_argument(
+ '-H', action='store', nargs=2, metavar=('NAME', 'TYPE'),
+ help='Print detailed help for the object <name> of type <type>, '
+ 'where <type> is one of "lexer", "formatter" or "filter".')
+ special_modes.add_argument(
+ '-V', action='store_true',
+ help='Print the package version.')
+ special_modes.add_argument(
+ '-h', '--help', action='store_true',
+ help='Print this help.')
+ special_modes_group.add_argument(
+ '-a', metavar='ARG',
+ help='Formatter-specific additional argument for the -S (print '
+ 'style sheet) mode.')
+
+ argns = parser.parse_args(args[1:])
+
+ try:
+ return main_inner(parser, argns)
+ except BrokenPipeError:
+ # someone closed our stdout, e.g. by quitting a pager.
+ return 0
+ except Exception:
+ if argns.v:
+ print(file=sys.stderr)
+ print('*' * 65, file=sys.stderr)
+ print('An unhandled exception occurred while highlighting.',
+ file=sys.stderr)
+ print('Please report the whole traceback to the issue tracker at',
+ file=sys.stderr)
+ print('<https://github.com/pygments/pygments/issues>.',
+ file=sys.stderr)
+ print('*' * 65, file=sys.stderr)
+ print(file=sys.stderr)
+ raise
+ import traceback
+ info = traceback.format_exception(*sys.exc_info())
+ msg = info[-1].strip()
+ if len(info) >= 3:
+ # extract relevant file and position info
+ msg += '\n (f%s)' % info[-2].split('\n')[0].strip()[1:]
+ print(file=sys.stderr)
+ print('*** Error while highlighting:', file=sys.stderr)
+ print(msg, file=sys.stderr)
+ print('*** If this is a bug you want to report, please rerun with -v.',
+ file=sys.stderr)
+ return 1
diff --git a/src/pip/_vendor/pygments/console.py b/src/pip/_vendor/pygments/console.py
new file mode 100644
index 000000000..2ada68e03
--- /dev/null
+++ b/src/pip/_vendor/pygments/console.py
@@ -0,0 +1,70 @@
+"""
+ pygments.console
+ ~~~~~~~~~~~~~~~~
+
+ Format colored console output.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+esc = "\x1b["
+
+codes = {}
+codes[""] = ""
+codes["reset"] = esc + "39;49;00m"
+
+codes["bold"] = esc + "01m"
+codes["faint"] = esc + "02m"
+codes["standout"] = esc + "03m"
+codes["underline"] = esc + "04m"
+codes["blink"] = esc + "05m"
+codes["overline"] = esc + "06m"
+
+dark_colors = ["black", "red", "green", "yellow", "blue",
+ "magenta", "cyan", "gray"]
+light_colors = ["brightblack", "brightred", "brightgreen", "brightyellow", "brightblue",
+ "brightmagenta", "brightcyan", "white"]
+
+x = 30
+for d, l in zip(dark_colors, light_colors):
+ codes[d] = esc + "%im" % x
+ codes[l] = esc + "%im" % (60 + x)
+ x += 1
+
+del d, l, x
+
+codes["white"] = codes["bold"]
+
+
+def reset_color():
+ return codes["reset"]
+
+
+def colorize(color_key, text):
+ return codes[color_key] + text + codes["reset"]
+
+
+def ansiformat(attr, text):
+ """
+ Format ``text`` with a color and/or some attributes::
+
+ color normal color
+ *color* bold color
+ _color_ underlined color
+ +color+ blinking color
+ """
+ result = []
+ if attr[:1] == attr[-1:] == '+':
+ result.append(codes['blink'])
+ attr = attr[1:-1]
+ if attr[:1] == attr[-1:] == '*':
+ result.append(codes['bold'])
+ attr = attr[1:-1]
+ if attr[:1] == attr[-1:] == '_':
+ result.append(codes['underline'])
+ attr = attr[1:-1]
+ result.append(codes[attr])
+ result.append(text)
+ result.append(codes['reset'])
+ return ''.join(result)
diff --git a/src/pip/_vendor/pygments/filter.py b/src/pip/_vendor/pygments/filter.py
new file mode 100644
index 000000000..e5c966493
--- /dev/null
+++ b/src/pip/_vendor/pygments/filter.py
@@ -0,0 +1,71 @@
+"""
+ pygments.filter
+ ~~~~~~~~~~~~~~~
+
+ Module that implements the default filter.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+
+def apply_filters(stream, filters, lexer=None):
+ """
+ Use this method to apply an iterable of filters to
+ a stream. If lexer is given it's forwarded to the
+ filter, otherwise the filter receives `None`.
+ """
+ def _apply(filter_, stream):
+ yield from filter_.filter(lexer, stream)
+ for filter_ in filters:
+ stream = _apply(filter_, stream)
+ return stream
+
+
+def simplefilter(f):
+ """
+ Decorator that converts a function into a filter::
+
+ @simplefilter
+ def lowercase(self, lexer, stream, options):
+ for ttype, value in stream:
+ yield ttype, value.lower()
+ """
+ return type(f.__name__, (FunctionFilter,), {
+ '__module__': getattr(f, '__module__'),
+ '__doc__': f.__doc__,
+ 'function': f,
+ })
+
+
+class Filter:
+ """
+ Default filter. Subclass this class or use the `simplefilter`
+ decorator to create own filters.
+ """
+
+ def __init__(self, **options):
+ self.options = options
+
+ def filter(self, lexer, stream):
+ raise NotImplementedError()
+
+
+class FunctionFilter(Filter):
+ """
+ Abstract class used by `simplefilter` to create simple
+ function filters on the fly. The `simplefilter` decorator
+ automatically creates subclasses of this class for
+ functions passed to it.
+ """
+ function = None
+
+ def __init__(self, **options):
+ if not hasattr(self, 'function'):
+ raise TypeError('%r used without bound function' %
+ self.__class__.__name__)
+ Filter.__init__(self, **options)
+
+ def filter(self, lexer, stream):
+ # pylint: disable=not-callable
+ yield from self.function(lexer, stream, self.options)
diff --git a/src/pip/_vendor/pygments/filters/__init__.py b/src/pip/_vendor/pygments/filters/__init__.py
new file mode 100644
index 000000000..c302a6c0c
--- /dev/null
+++ b/src/pip/_vendor/pygments/filters/__init__.py
@@ -0,0 +1,940 @@
+"""
+ pygments.filters
+ ~~~~~~~~~~~~~~~~
+
+ Module containing filter lookup functions and default
+ filters.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import re
+
+from pip._vendor.pygments.token import String, Comment, Keyword, Name, Error, Whitespace, \
+ string_to_tokentype
+from pip._vendor.pygments.filter import Filter
+from pip._vendor.pygments.util import get_list_opt, get_int_opt, get_bool_opt, \
+ get_choice_opt, ClassNotFound, OptionError
+from pip._vendor.pygments.plugin import find_plugin_filters
+
+
+def find_filter_class(filtername):
+ """Lookup a filter by name. Return None if not found."""
+ if filtername in FILTERS:
+ return FILTERS[filtername]
+ for name, cls in find_plugin_filters():
+ if name == filtername:
+ return cls
+ return None
+
+
+def get_filter_by_name(filtername, **options):
+ """Return an instantiated filter.
+
+ Options are passed to the filter initializer if wanted.
+ Raise a ClassNotFound if not found.
+ """
+ cls = find_filter_class(filtername)
+ if cls:
+ return cls(**options)
+ else:
+ raise ClassNotFound('filter %r not found' % filtername)
+
+
+def get_all_filters():
+ """Return a generator of all filter names."""
+ yield from FILTERS
+ for name, _ in find_plugin_filters():
+ yield name
+
+
+def _replace_special(ttype, value, regex, specialttype,
+ replacefunc=lambda x: x):
+ last = 0
+ for match in regex.finditer(value):
+ start, end = match.start(), match.end()
+ if start != last:
+ yield ttype, value[last:start]
+ yield specialttype, replacefunc(value[start:end])
+ last = end
+ if last != len(value):
+ yield ttype, value[last:]
+
+
+class CodeTagFilter(Filter):
+ """Highlight special code tags in comments and docstrings.
+
+ Options accepted:
+
+ `codetags` : list of strings
+ A list of strings that are flagged as code tags. The default is to
+ highlight ``XXX``, ``TODO``, ``FIXME``, ``BUG`` and ``NOTE``.
+
+ .. versionchanged:: 2.13
+ Now recognizes ``FIXME`` by default.
+ """
+
+ def __init__(self, **options):
+ Filter.__init__(self, **options)
+ tags = get_list_opt(options, 'codetags',
+ ['XXX', 'TODO', 'FIXME', 'BUG', 'NOTE'])
+ self.tag_re = re.compile(r'\b(%s)\b' % '|'.join([
+ re.escape(tag) for tag in tags if tag
+ ]))
+
+ def filter(self, lexer, stream):
+ regex = self.tag_re
+ for ttype, value in stream:
+ if ttype in String.Doc or \
+ ttype in Comment and \
+ ttype not in Comment.Preproc:
+ yield from _replace_special(ttype, value, regex, Comment.Special)
+ else:
+ yield ttype, value
+
+
+class SymbolFilter(Filter):
+ """Convert mathematical symbols such as \\<longrightarrow> in Isabelle
+ or \\longrightarrow in LaTeX into Unicode characters.
+
+ This is mostly useful for HTML or console output when you want to
+ approximate the source rendering you'd see in an IDE.
+
+ Options accepted:
+
+ `lang` : string
+ The symbol language. Must be one of ``'isabelle'`` or
+ ``'latex'``. The default is ``'isabelle'``.
+ """
+
+ latex_symbols = {
+ '\\alpha' : '\U000003b1',
+ '\\beta' : '\U000003b2',
+ '\\gamma' : '\U000003b3',
+ '\\delta' : '\U000003b4',
+ '\\varepsilon' : '\U000003b5',
+ '\\zeta' : '\U000003b6',
+ '\\eta' : '\U000003b7',
+ '\\vartheta' : '\U000003b8',
+ '\\iota' : '\U000003b9',
+ '\\kappa' : '\U000003ba',
+ '\\lambda' : '\U000003bb',
+ '\\mu' : '\U000003bc',
+ '\\nu' : '\U000003bd',
+ '\\xi' : '\U000003be',
+ '\\pi' : '\U000003c0',
+ '\\varrho' : '\U000003c1',
+ '\\sigma' : '\U000003c3',
+ '\\tau' : '\U000003c4',
+ '\\upsilon' : '\U000003c5',
+ '\\varphi' : '\U000003c6',
+ '\\chi' : '\U000003c7',
+ '\\psi' : '\U000003c8',
+ '\\omega' : '\U000003c9',
+ '\\Gamma' : '\U00000393',
+ '\\Delta' : '\U00000394',
+ '\\Theta' : '\U00000398',
+ '\\Lambda' : '\U0000039b',
+ '\\Xi' : '\U0000039e',
+ '\\Pi' : '\U000003a0',
+ '\\Sigma' : '\U000003a3',
+ '\\Upsilon' : '\U000003a5',
+ '\\Phi' : '\U000003a6',
+ '\\Psi' : '\U000003a8',
+ '\\Omega' : '\U000003a9',
+ '\\leftarrow' : '\U00002190',
+ '\\longleftarrow' : '\U000027f5',
+ '\\rightarrow' : '\U00002192',
+ '\\longrightarrow' : '\U000027f6',
+ '\\Leftarrow' : '\U000021d0',
+ '\\Longleftarrow' : '\U000027f8',
+ '\\Rightarrow' : '\U000021d2',
+ '\\Longrightarrow' : '\U000027f9',
+ '\\leftrightarrow' : '\U00002194',
+ '\\longleftrightarrow' : '\U000027f7',
+ '\\Leftrightarrow' : '\U000021d4',
+ '\\Longleftrightarrow' : '\U000027fa',
+ '\\mapsto' : '\U000021a6',
+ '\\longmapsto' : '\U000027fc',
+ '\\relbar' : '\U00002500',
+ '\\Relbar' : '\U00002550',
+ '\\hookleftarrow' : '\U000021a9',
+ '\\hookrightarrow' : '\U000021aa',
+ '\\leftharpoondown' : '\U000021bd',
+ '\\rightharpoondown' : '\U000021c1',
+ '\\leftharpoonup' : '\U000021bc',
+ '\\rightharpoonup' : '\U000021c0',
+ '\\rightleftharpoons' : '\U000021cc',
+ '\\leadsto' : '\U0000219d',
+ '\\downharpoonleft' : '\U000021c3',
+ '\\downharpoonright' : '\U000021c2',
+ '\\upharpoonleft' : '\U000021bf',
+ '\\upharpoonright' : '\U000021be',
+ '\\restriction' : '\U000021be',
+ '\\uparrow' : '\U00002191',
+ '\\Uparrow' : '\U000021d1',
+ '\\downarrow' : '\U00002193',
+ '\\Downarrow' : '\U000021d3',
+ '\\updownarrow' : '\U00002195',
+ '\\Updownarrow' : '\U000021d5',
+ '\\langle' : '\U000027e8',
+ '\\rangle' : '\U000027e9',
+ '\\lceil' : '\U00002308',
+ '\\rceil' : '\U00002309',
+ '\\lfloor' : '\U0000230a',
+ '\\rfloor' : '\U0000230b',
+ '\\flqq' : '\U000000ab',
+ '\\frqq' : '\U000000bb',
+ '\\bot' : '\U000022a5',
+ '\\top' : '\U000022a4',
+ '\\wedge' : '\U00002227',
+ '\\bigwedge' : '\U000022c0',
+ '\\vee' : '\U00002228',
+ '\\bigvee' : '\U000022c1',
+ '\\forall' : '\U00002200',
+ '\\exists' : '\U00002203',
+ '\\nexists' : '\U00002204',
+ '\\neg' : '\U000000ac',
+ '\\Box' : '\U000025a1',
+ '\\Diamond' : '\U000025c7',
+ '\\vdash' : '\U000022a2',
+ '\\models' : '\U000022a8',
+ '\\dashv' : '\U000022a3',
+ '\\surd' : '\U0000221a',
+ '\\le' : '\U00002264',
+ '\\ge' : '\U00002265',
+ '\\ll' : '\U0000226a',
+ '\\gg' : '\U0000226b',
+ '\\lesssim' : '\U00002272',
+ '\\gtrsim' : '\U00002273',
+ '\\lessapprox' : '\U00002a85',
+ '\\gtrapprox' : '\U00002a86',
+ '\\in' : '\U00002208',
+ '\\notin' : '\U00002209',
+ '\\subset' : '\U00002282',
+ '\\supset' : '\U00002283',
+ '\\subseteq' : '\U00002286',
+ '\\supseteq' : '\U00002287',
+ '\\sqsubset' : '\U0000228f',
+ '\\sqsupset' : '\U00002290',
+ '\\sqsubseteq' : '\U00002291',
+ '\\sqsupseteq' : '\U00002292',
+ '\\cap' : '\U00002229',
+ '\\bigcap' : '\U000022c2',
+ '\\cup' : '\U0000222a',
+ '\\bigcup' : '\U000022c3',
+ '\\sqcup' : '\U00002294',
+ '\\bigsqcup' : '\U00002a06',
+ '\\sqcap' : '\U00002293',
+ '\\Bigsqcap' : '\U00002a05',
+ '\\setminus' : '\U00002216',
+ '\\propto' : '\U0000221d',
+ '\\uplus' : '\U0000228e',
+ '\\bigplus' : '\U00002a04',
+ '\\sim' : '\U0000223c',
+ '\\doteq' : '\U00002250',
+ '\\simeq' : '\U00002243',
+ '\\approx' : '\U00002248',
+ '\\asymp' : '\U0000224d',
+ '\\cong' : '\U00002245',
+ '\\equiv' : '\U00002261',
+ '\\Join' : '\U000022c8',
+ '\\bowtie' : '\U00002a1d',
+ '\\prec' : '\U0000227a',
+ '\\succ' : '\U0000227b',
+ '\\preceq' : '\U0000227c',
+ '\\succeq' : '\U0000227d',
+ '\\parallel' : '\U00002225',
+ '\\mid' : '\U000000a6',
+ '\\pm' : '\U000000b1',
+ '\\mp' : '\U00002213',
+ '\\times' : '\U000000d7',
+ '\\div' : '\U000000f7',
+ '\\cdot' : '\U000022c5',
+ '\\star' : '\U000022c6',
+ '\\circ' : '\U00002218',
+ '\\dagger' : '\U00002020',
+ '\\ddagger' : '\U00002021',
+ '\\lhd' : '\U000022b2',
+ '\\rhd' : '\U000022b3',
+ '\\unlhd' : '\U000022b4',
+ '\\unrhd' : '\U000022b5',
+ '\\triangleleft' : '\U000025c3',
+ '\\triangleright' : '\U000025b9',
+ '\\triangle' : '\U000025b3',
+ '\\triangleq' : '\U0000225c',
+ '\\oplus' : '\U00002295',
+ '\\bigoplus' : '\U00002a01',
+ '\\otimes' : '\U00002297',
+ '\\bigotimes' : '\U00002a02',
+ '\\odot' : '\U00002299',
+ '\\bigodot' : '\U00002a00',
+ '\\ominus' : '\U00002296',
+ '\\oslash' : '\U00002298',
+ '\\dots' : '\U00002026',
+ '\\cdots' : '\U000022ef',
+ '\\sum' : '\U00002211',
+ '\\prod' : '\U0000220f',
+ '\\coprod' : '\U00002210',
+ '\\infty' : '\U0000221e',
+ '\\int' : '\U0000222b',
+ '\\oint' : '\U0000222e',
+ '\\clubsuit' : '\U00002663',
+ '\\diamondsuit' : '\U00002662',
+ '\\heartsuit' : '\U00002661',
+ '\\spadesuit' : '\U00002660',
+ '\\aleph' : '\U00002135',
+ '\\emptyset' : '\U00002205',
+ '\\nabla' : '\U00002207',
+ '\\partial' : '\U00002202',
+ '\\flat' : '\U0000266d',
+ '\\natural' : '\U0000266e',
+ '\\sharp' : '\U0000266f',
+ '\\angle' : '\U00002220',
+ '\\copyright' : '\U000000a9',
+ '\\textregistered' : '\U000000ae',
+ '\\textonequarter' : '\U000000bc',
+ '\\textonehalf' : '\U000000bd',
+ '\\textthreequarters' : '\U000000be',
+ '\\textordfeminine' : '\U000000aa',
+ '\\textordmasculine' : '\U000000ba',
+ '\\euro' : '\U000020ac',
+ '\\pounds' : '\U000000a3',
+ '\\yen' : '\U000000a5',
+ '\\textcent' : '\U000000a2',
+ '\\textcurrency' : '\U000000a4',
+ '\\textdegree' : '\U000000b0',
+ }
+
+ isabelle_symbols = {
+ '\\<zero>' : '\U0001d7ec',
+ '\\<one>' : '\U0001d7ed',
+ '\\<two>' : '\U0001d7ee',
+ '\\<three>' : '\U0001d7ef',
+ '\\<four>' : '\U0001d7f0',
+ '\\<five>' : '\U0001d7f1',
+ '\\<six>' : '\U0001d7f2',
+ '\\<seven>' : '\U0001d7f3',
+ '\\<eight>' : '\U0001d7f4',
+ '\\<nine>' : '\U0001d7f5',
+ '\\<A>' : '\U0001d49c',
+ '\\<B>' : '\U0000212c',
+ '\\<C>' : '\U0001d49e',
+ '\\<D>' : '\U0001d49f',
+ '\\<E>' : '\U00002130',
+ '\\<F>' : '\U00002131',
+ '\\<G>' : '\U0001d4a2',
+ '\\<H>' : '\U0000210b',
+ '\\<I>' : '\U00002110',
+ '\\<J>' : '\U0001d4a5',
+ '\\<K>' : '\U0001d4a6',
+ '\\<L>' : '\U00002112',
+ '\\<M>' : '\U00002133',
+ '\\<N>' : '\U0001d4a9',
+ '\\<O>' : '\U0001d4aa',
+ '\\<P>' : '\U0001d4ab',
+ '\\<Q>' : '\U0001d4ac',
+ '\\<R>' : '\U0000211b',
+ '\\<S>' : '\U0001d4ae',
+ '\\<T>' : '\U0001d4af',
+ '\\<U>' : '\U0001d4b0',
+ '\\<V>' : '\U0001d4b1',
+ '\\<W>' : '\U0001d4b2',
+ '\\<X>' : '\U0001d4b3',
+ '\\<Y>' : '\U0001d4b4',
+ '\\<Z>' : '\U0001d4b5',
+ '\\<a>' : '\U0001d5ba',
+ '\\<b>' : '\U0001d5bb',
+ '\\<c>' : '\U0001d5bc',
+ '\\<d>' : '\U0001d5bd',
+ '\\<e>' : '\U0001d5be',
+ '\\<f>' : '\U0001d5bf',
+ '\\<g>' : '\U0001d5c0',
+ '\\<h>' : '\U0001d5c1',
+ '\\<i>' : '\U0001d5c2',
+ '\\<j>' : '\U0001d5c3',
+ '\\<k>' : '\U0001d5c4',
+ '\\<l>' : '\U0001d5c5',
+ '\\<m>' : '\U0001d5c6',
+ '\\<n>' : '\U0001d5c7',
+ '\\<o>' : '\U0001d5c8',
+ '\\<p>' : '\U0001d5c9',
+ '\\<q>' : '\U0001d5ca',
+ '\\<r>' : '\U0001d5cb',
+ '\\<s>' : '\U0001d5cc',
+ '\\<t>' : '\U0001d5cd',
+ '\\<u>' : '\U0001d5ce',
+ '\\<v>' : '\U0001d5cf',
+ '\\<w>' : '\U0001d5d0',
+ '\\<x>' : '\U0001d5d1',
+ '\\<y>' : '\U0001d5d2',
+ '\\<z>' : '\U0001d5d3',
+ '\\<AA>' : '\U0001d504',
+ '\\<BB>' : '\U0001d505',
+ '\\<CC>' : '\U0000212d',
+ '\\<DD>' : '\U0001d507',
+ '\\<EE>' : '\U0001d508',
+ '\\<FF>' : '\U0001d509',
+ '\\<GG>' : '\U0001d50a',
+ '\\<HH>' : '\U0000210c',
+ '\\<II>' : '\U00002111',
+ '\\<JJ>' : '\U0001d50d',
+ '\\<KK>' : '\U0001d50e',
+ '\\<LL>' : '\U0001d50f',
+ '\\<MM>' : '\U0001d510',
+ '\\<NN>' : '\U0001d511',
+ '\\<OO>' : '\U0001d512',
+ '\\<PP>' : '\U0001d513',
+ '\\<QQ>' : '\U0001d514',
+ '\\<RR>' : '\U0000211c',
+ '\\<SS>' : '\U0001d516',
+ '\\<TT>' : '\U0001d517',
+ '\\<UU>' : '\U0001d518',
+ '\\<VV>' : '\U0001d519',
+ '\\<WW>' : '\U0001d51a',
+ '\\<XX>' : '\U0001d51b',
+ '\\<YY>' : '\U0001d51c',
+ '\\<ZZ>' : '\U00002128',
+ '\\<aa>' : '\U0001d51e',
+ '\\<bb>' : '\U0001d51f',
+ '\\<cc>' : '\U0001d520',
+ '\\<dd>' : '\U0001d521',
+ '\\<ee>' : '\U0001d522',
+ '\\<ff>' : '\U0001d523',
+ '\\<gg>' : '\U0001d524',
+ '\\<hh>' : '\U0001d525',
+ '\\<ii>' : '\U0001d526',
+ '\\<jj>' : '\U0001d527',
+ '\\<kk>' : '\U0001d528',
+ '\\<ll>' : '\U0001d529',
+ '\\<mm>' : '\U0001d52a',
+ '\\<nn>' : '\U0001d52b',
+ '\\<oo>' : '\U0001d52c',
+ '\\<pp>' : '\U0001d52d',
+ '\\<qq>' : '\U0001d52e',
+ '\\<rr>' : '\U0001d52f',
+ '\\<ss>' : '\U0001d530',
+ '\\<tt>' : '\U0001d531',
+ '\\<uu>' : '\U0001d532',
+ '\\<vv>' : '\U0001d533',
+ '\\<ww>' : '\U0001d534',
+ '\\<xx>' : '\U0001d535',
+ '\\<yy>' : '\U0001d536',
+ '\\<zz>' : '\U0001d537',
+ '\\<alpha>' : '\U000003b1',
+ '\\<beta>' : '\U000003b2',
+ '\\<gamma>' : '\U000003b3',
+ '\\<delta>' : '\U000003b4',
+ '\\<epsilon>' : '\U000003b5',
+ '\\<zeta>' : '\U000003b6',
+ '\\<eta>' : '\U000003b7',
+ '\\<theta>' : '\U000003b8',
+ '\\<iota>' : '\U000003b9',
+ '\\<kappa>' : '\U000003ba',
+ '\\<lambda>' : '\U000003bb',
+ '\\<mu>' : '\U000003bc',
+ '\\<nu>' : '\U000003bd',
+ '\\<xi>' : '\U000003be',
+ '\\<pi>' : '\U000003c0',
+ '\\<rho>' : '\U000003c1',
+ '\\<sigma>' : '\U000003c3',
+ '\\<tau>' : '\U000003c4',
+ '\\<upsilon>' : '\U000003c5',
+ '\\<phi>' : '\U000003c6',
+ '\\<chi>' : '\U000003c7',
+ '\\<psi>' : '\U000003c8',
+ '\\<omega>' : '\U000003c9',
+ '\\<Gamma>' : '\U00000393',
+ '\\<Delta>' : '\U00000394',
+ '\\<Theta>' : '\U00000398',
+ '\\<Lambda>' : '\U0000039b',
+ '\\<Xi>' : '\U0000039e',
+ '\\<Pi>' : '\U000003a0',
+ '\\<Sigma>' : '\U000003a3',
+ '\\<Upsilon>' : '\U000003a5',
+ '\\<Phi>' : '\U000003a6',
+ '\\<Psi>' : '\U000003a8',
+ '\\<Omega>' : '\U000003a9',
+ '\\<bool>' : '\U0001d539',
+ '\\<complex>' : '\U00002102',
+ '\\<nat>' : '\U00002115',
+ '\\<rat>' : '\U0000211a',
+ '\\<real>' : '\U0000211d',
+ '\\<int>' : '\U00002124',
+ '\\<leftarrow>' : '\U00002190',
+ '\\<longleftarrow>' : '\U000027f5',
+ '\\<rightarrow>' : '\U00002192',
+ '\\<longrightarrow>' : '\U000027f6',
+ '\\<Leftarrow>' : '\U000021d0',
+ '\\<Longleftarrow>' : '\U000027f8',
+ '\\<Rightarrow>' : '\U000021d2',
+ '\\<Longrightarrow>' : '\U000027f9',
+ '\\<leftrightarrow>' : '\U00002194',
+ '\\<longleftrightarrow>' : '\U000027f7',
+ '\\<Leftrightarrow>' : '\U000021d4',
+ '\\<Longleftrightarrow>' : '\U000027fa',
+ '\\<mapsto>' : '\U000021a6',
+ '\\<longmapsto>' : '\U000027fc',
+ '\\<midarrow>' : '\U00002500',
+ '\\<Midarrow>' : '\U00002550',
+ '\\<hookleftarrow>' : '\U000021a9',
+ '\\<hookrightarrow>' : '\U000021aa',
+ '\\<leftharpoondown>' : '\U000021bd',
+ '\\<rightharpoondown>' : '\U000021c1',
+ '\\<leftharpoonup>' : '\U000021bc',
+ '\\<rightharpoonup>' : '\U000021c0',
+ '\\<rightleftharpoons>' : '\U000021cc',
+ '\\<leadsto>' : '\U0000219d',
+ '\\<downharpoonleft>' : '\U000021c3',
+ '\\<downharpoonright>' : '\U000021c2',
+ '\\<upharpoonleft>' : '\U000021bf',
+ '\\<upharpoonright>' : '\U000021be',
+ '\\<restriction>' : '\U000021be',
+ '\\<Colon>' : '\U00002237',
+ '\\<up>' : '\U00002191',
+ '\\<Up>' : '\U000021d1',
+ '\\<down>' : '\U00002193',
+ '\\<Down>' : '\U000021d3',
+ '\\<updown>' : '\U00002195',
+ '\\<Updown>' : '\U000021d5',
+ '\\<langle>' : '\U000027e8',
+ '\\<rangle>' : '\U000027e9',
+ '\\<lceil>' : '\U00002308',
+ '\\<rceil>' : '\U00002309',
+ '\\<lfloor>' : '\U0000230a',
+ '\\<rfloor>' : '\U0000230b',
+ '\\<lparr>' : '\U00002987',
+ '\\<rparr>' : '\U00002988',
+ '\\<lbrakk>' : '\U000027e6',
+ '\\<rbrakk>' : '\U000027e7',
+ '\\<lbrace>' : '\U00002983',
+ '\\<rbrace>' : '\U00002984',
+ '\\<guillemotleft>' : '\U000000ab',
+ '\\<guillemotright>' : '\U000000bb',
+ '\\<bottom>' : '\U000022a5',
+ '\\<top>' : '\U000022a4',
+ '\\<and>' : '\U00002227',
+ '\\<And>' : '\U000022c0',
+ '\\<or>' : '\U00002228',
+ '\\<Or>' : '\U000022c1',
+ '\\<forall>' : '\U00002200',
+ '\\<exists>' : '\U00002203',
+ '\\<nexists>' : '\U00002204',
+ '\\<not>' : '\U000000ac',
+ '\\<box>' : '\U000025a1',
+ '\\<diamond>' : '\U000025c7',
+ '\\<turnstile>' : '\U000022a2',
+ '\\<Turnstile>' : '\U000022a8',
+ '\\<tturnstile>' : '\U000022a9',
+ '\\<TTurnstile>' : '\U000022ab',
+ '\\<stileturn>' : '\U000022a3',
+ '\\<surd>' : '\U0000221a',
+ '\\<le>' : '\U00002264',
+ '\\<ge>' : '\U00002265',
+ '\\<lless>' : '\U0000226a',
+ '\\<ggreater>' : '\U0000226b',
+ '\\<lesssim>' : '\U00002272',
+ '\\<greatersim>' : '\U00002273',
+ '\\<lessapprox>' : '\U00002a85',
+ '\\<greaterapprox>' : '\U00002a86',
+ '\\<in>' : '\U00002208',
+ '\\<notin>' : '\U00002209',
+ '\\<subset>' : '\U00002282',
+ '\\<supset>' : '\U00002283',
+ '\\<subseteq>' : '\U00002286',
+ '\\<supseteq>' : '\U00002287',
+ '\\<sqsubset>' : '\U0000228f',
+ '\\<sqsupset>' : '\U00002290',
+ '\\<sqsubseteq>' : '\U00002291',
+ '\\<sqsupseteq>' : '\U00002292',
+ '\\<inter>' : '\U00002229',
+ '\\<Inter>' : '\U000022c2',
+ '\\<union>' : '\U0000222a',
+ '\\<Union>' : '\U000022c3',
+ '\\<squnion>' : '\U00002294',
+ '\\<Squnion>' : '\U00002a06',
+ '\\<sqinter>' : '\U00002293',
+ '\\<Sqinter>' : '\U00002a05',
+ '\\<setminus>' : '\U00002216',
+ '\\<propto>' : '\U0000221d',
+ '\\<uplus>' : '\U0000228e',
+ '\\<Uplus>' : '\U00002a04',
+ '\\<noteq>' : '\U00002260',
+ '\\<sim>' : '\U0000223c',
+ '\\<doteq>' : '\U00002250',
+ '\\<simeq>' : '\U00002243',
+ '\\<approx>' : '\U00002248',
+ '\\<asymp>' : '\U0000224d',
+ '\\<cong>' : '\U00002245',
+ '\\<smile>' : '\U00002323',
+ '\\<equiv>' : '\U00002261',
+ '\\<frown>' : '\U00002322',
+ '\\<Join>' : '\U000022c8',
+ '\\<bowtie>' : '\U00002a1d',
+ '\\<prec>' : '\U0000227a',
+ '\\<succ>' : '\U0000227b',
+ '\\<preceq>' : '\U0000227c',
+ '\\<succeq>' : '\U0000227d',
+ '\\<parallel>' : '\U00002225',
+ '\\<bar>' : '\U000000a6',
+ '\\<plusminus>' : '\U000000b1',
+ '\\<minusplus>' : '\U00002213',
+ '\\<times>' : '\U000000d7',
+ '\\<div>' : '\U000000f7',
+ '\\<cdot>' : '\U000022c5',
+ '\\<star>' : '\U000022c6',
+ '\\<bullet>' : '\U00002219',
+ '\\<circ>' : '\U00002218',
+ '\\<dagger>' : '\U00002020',
+ '\\<ddagger>' : '\U00002021',
+ '\\<lhd>' : '\U000022b2',
+ '\\<rhd>' : '\U000022b3',
+ '\\<unlhd>' : '\U000022b4',
+ '\\<unrhd>' : '\U000022b5',
+ '\\<triangleleft>' : '\U000025c3',
+ '\\<triangleright>' : '\U000025b9',
+ '\\<triangle>' : '\U000025b3',
+ '\\<triangleq>' : '\U0000225c',
+ '\\<oplus>' : '\U00002295',
+ '\\<Oplus>' : '\U00002a01',
+ '\\<otimes>' : '\U00002297',
+ '\\<Otimes>' : '\U00002a02',
+ '\\<odot>' : '\U00002299',
+ '\\<Odot>' : '\U00002a00',
+ '\\<ominus>' : '\U00002296',
+ '\\<oslash>' : '\U00002298',
+ '\\<dots>' : '\U00002026',
+ '\\<cdots>' : '\U000022ef',
+ '\\<Sum>' : '\U00002211',
+ '\\<Prod>' : '\U0000220f',
+ '\\<Coprod>' : '\U00002210',
+ '\\<infinity>' : '\U0000221e',
+ '\\<integral>' : '\U0000222b',
+ '\\<ointegral>' : '\U0000222e',
+ '\\<clubsuit>' : '\U00002663',
+ '\\<diamondsuit>' : '\U00002662',
+ '\\<heartsuit>' : '\U00002661',
+ '\\<spadesuit>' : '\U00002660',
+ '\\<aleph>' : '\U00002135',
+ '\\<emptyset>' : '\U00002205',
+ '\\<nabla>' : '\U00002207',
+ '\\<partial>' : '\U00002202',
+ '\\<flat>' : '\U0000266d',
+ '\\<natural>' : '\U0000266e',
+ '\\<sharp>' : '\U0000266f',
+ '\\<angle>' : '\U00002220',
+ '\\<copyright>' : '\U000000a9',
+ '\\<registered>' : '\U000000ae',
+ '\\<hyphen>' : '\U000000ad',
+ '\\<inverse>' : '\U000000af',
+ '\\<onequarter>' : '\U000000bc',
+ '\\<onehalf>' : '\U000000bd',
+ '\\<threequarters>' : '\U000000be',
+ '\\<ordfeminine>' : '\U000000aa',
+ '\\<ordmasculine>' : '\U000000ba',
+ '\\<section>' : '\U000000a7',
+ '\\<paragraph>' : '\U000000b6',
+ '\\<exclamdown>' : '\U000000a1',
+ '\\<questiondown>' : '\U000000bf',
+ '\\<euro>' : '\U000020ac',
+ '\\<pounds>' : '\U000000a3',
+ '\\<yen>' : '\U000000a5',
+ '\\<cent>' : '\U000000a2',
+ '\\<currency>' : '\U000000a4',
+ '\\<degree>' : '\U000000b0',
+ '\\<amalg>' : '\U00002a3f',
+ '\\<mho>' : '\U00002127',
+ '\\<lozenge>' : '\U000025ca',
+ '\\<wp>' : '\U00002118',
+ '\\<wrong>' : '\U00002240',
+ '\\<struct>' : '\U000022c4',
+ '\\<acute>' : '\U000000b4',
+ '\\<index>' : '\U00000131',
+ '\\<dieresis>' : '\U000000a8',
+ '\\<cedilla>' : '\U000000b8',
+ '\\<hungarumlaut>' : '\U000002dd',
+ '\\<some>' : '\U000003f5',
+ '\\<newline>' : '\U000023ce',
+ '\\<open>' : '\U00002039',
+ '\\<close>' : '\U0000203a',
+ '\\<here>' : '\U00002302',
+ '\\<^sub>' : '\U000021e9',
+ '\\<^sup>' : '\U000021e7',
+ '\\<^bold>' : '\U00002759',
+ '\\<^bsub>' : '\U000021d8',
+ '\\<^esub>' : '\U000021d9',
+ '\\<^bsup>' : '\U000021d7',
+ '\\<^esup>' : '\U000021d6',
+ }
+
+ lang_map = {'isabelle' : isabelle_symbols, 'latex' : latex_symbols}
+
+ def __init__(self, **options):
+ Filter.__init__(self, **options)
+ lang = get_choice_opt(options, 'lang',
+ ['isabelle', 'latex'], 'isabelle')
+ self.symbols = self.lang_map[lang]
+
+ def filter(self, lexer, stream):
+ for ttype, value in stream:
+ if value in self.symbols:
+ yield ttype, self.symbols[value]
+ else:
+ yield ttype, value
+
+
+class KeywordCaseFilter(Filter):
+ """Convert keywords to lowercase or uppercase or capitalize them, which
+ means first letter uppercase, rest lowercase.
+
+ This can be useful e.g. if you highlight Pascal code and want to adapt the
+ code to your styleguide.
+
+ Options accepted:
+
+ `case` : string
+ The casing to convert keywords to. Must be one of ``'lower'``,
+ ``'upper'`` or ``'capitalize'``. The default is ``'lower'``.
+ """
+
+ def __init__(self, **options):
+ Filter.__init__(self, **options)
+ case = get_choice_opt(options, 'case',
+ ['lower', 'upper', 'capitalize'], 'lower')
+ self.convert = getattr(str, case)
+
+ def filter(self, lexer, stream):
+ for ttype, value in stream:
+ if ttype in Keyword:
+ yield ttype, self.convert(value)
+ else:
+ yield ttype, value
+
+
+class NameHighlightFilter(Filter):
+ """Highlight a normal Name (and Name.*) token with a different token type.
+
+ Example::
+
+ filter = NameHighlightFilter(
+ names=['foo', 'bar', 'baz'],
+ tokentype=Name.Function,
+ )
+
+ This would highlight the names "foo", "bar" and "baz"
+ as functions. `Name.Function` is the default token type.
+
+ Options accepted:
+
+ `names` : list of strings
+ A list of names that should be given the different token type.
+ There is no default.
+ `tokentype` : TokenType or string
+ A token type or a string containing a token type name that is
+ used for highlighting the strings in `names`. The default is
+ `Name.Function`.
+ """
+
+ def __init__(self, **options):
+ Filter.__init__(self, **options)
+ self.names = set(get_list_opt(options, 'names', []))
+ tokentype = options.get('tokentype')
+ if tokentype:
+ self.tokentype = string_to_tokentype(tokentype)
+ else:
+ self.tokentype = Name.Function
+
+ def filter(self, lexer, stream):
+ for ttype, value in stream:
+ if ttype in Name and value in self.names:
+ yield self.tokentype, value
+ else:
+ yield ttype, value
+
+
+class ErrorToken(Exception):
+ pass
+
+
+class RaiseOnErrorTokenFilter(Filter):
+ """Raise an exception when the lexer generates an error token.
+
+ Options accepted:
+
+ `excclass` : Exception class
+ The exception class to raise.
+ The default is `pygments.filters.ErrorToken`.
+
+ .. versionadded:: 0.8
+ """
+
+ def __init__(self, **options):
+ Filter.__init__(self, **options)
+ self.exception = options.get('excclass', ErrorToken)
+ try:
+ # issubclass() will raise TypeError if first argument is not a class
+ if not issubclass(self.exception, Exception):
+ raise TypeError
+ except TypeError:
+ raise OptionError('excclass option is not an exception class')
+
+ def filter(self, lexer, stream):
+ for ttype, value in stream:
+ if ttype is Error:
+ raise self.exception(value)
+ yield ttype, value
+
+
+class VisibleWhitespaceFilter(Filter):
+ """Convert tabs, newlines and/or spaces to visible characters.
+
+ Options accepted:
+
+ `spaces` : string or bool
+ If this is a one-character string, spaces will be replaces by this string.
+ If it is another true value, spaces will be replaced by ``·`` (unicode
+ MIDDLE DOT). If it is a false value, spaces will not be replaced. The
+ default is ``False``.
+ `tabs` : string or bool
+ The same as for `spaces`, but the default replacement character is ``»``
+ (unicode RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK). The default value
+ is ``False``. Note: this will not work if the `tabsize` option for the
+ lexer is nonzero, as tabs will already have been expanded then.
+ `tabsize` : int
+ If tabs are to be replaced by this filter (see the `tabs` option), this
+ is the total number of characters that a tab should be expanded to.
+ The default is ``8``.
+ `newlines` : string or bool
+ The same as for `spaces`, but the default replacement character is ``¶``
+ (unicode PILCROW SIGN). The default value is ``False``.
+ `wstokentype` : bool
+ If true, give whitespace the special `Whitespace` token type. This allows
+ styling the visible whitespace differently (e.g. greyed out), but it can
+ disrupt background colors. The default is ``True``.
+
+ .. versionadded:: 0.8
+ """
+
+ def __init__(self, **options):
+ Filter.__init__(self, **options)
+ for name, default in [('spaces', '·'),
+ ('tabs', '»'),
+ ('newlines', '¶')]:
+ opt = options.get(name, False)
+ if isinstance(opt, str) and len(opt) == 1:
+ setattr(self, name, opt)
+ else:
+ setattr(self, name, (opt and default or ''))
+ tabsize = get_int_opt(options, 'tabsize', 8)
+ if self.tabs:
+ self.tabs += ' ' * (tabsize - 1)
+ if self.newlines:
+ self.newlines += '\n'
+ self.wstt = get_bool_opt(options, 'wstokentype', True)
+
+ def filter(self, lexer, stream):
+ if self.wstt:
+ spaces = self.spaces or ' '
+ tabs = self.tabs or '\t'
+ newlines = self.newlines or '\n'
+ regex = re.compile(r'\s')
+
+ def replacefunc(wschar):
+ if wschar == ' ':
+ return spaces
+ elif wschar == '\t':
+ return tabs
+ elif wschar == '\n':
+ return newlines
+ return wschar
+
+ for ttype, value in stream:
+ yield from _replace_special(ttype, value, regex, Whitespace,
+ replacefunc)
+ else:
+ spaces, tabs, newlines = self.spaces, self.tabs, self.newlines
+ # simpler processing
+ for ttype, value in stream:
+ if spaces:
+ value = value.replace(' ', spaces)
+ if tabs:
+ value = value.replace('\t', tabs)
+ if newlines:
+ value = value.replace('\n', newlines)
+ yield ttype, value
+
+
+class GobbleFilter(Filter):
+ """Gobbles source code lines (eats initial characters).
+
+ This filter drops the first ``n`` characters off every line of code. This
+ may be useful when the source code fed to the lexer is indented by a fixed
+ amount of space that isn't desired in the output.
+
+ Options accepted:
+
+ `n` : int
+ The number of characters to gobble.
+
+ .. versionadded:: 1.2
+ """
+ def __init__(self, **options):
+ Filter.__init__(self, **options)
+ self.n = get_int_opt(options, 'n', 0)
+
+ def gobble(self, value, left):
+ if left < len(value):
+ return value[left:], 0
+ else:
+ return '', left - len(value)
+
+ def filter(self, lexer, stream):
+ n = self.n
+ left = n # How many characters left to gobble.
+ for ttype, value in stream:
+ # Remove ``left`` tokens from first line, ``n`` from all others.
+ parts = value.split('\n')
+ (parts[0], left) = self.gobble(parts[0], left)
+ for i in range(1, len(parts)):
+ (parts[i], left) = self.gobble(parts[i], n)
+ value = '\n'.join(parts)
+
+ if value != '':
+ yield ttype, value
+
+
+class TokenMergeFilter(Filter):
+ """Merges consecutive tokens with the same token type in the output
+ stream of a lexer.
+
+ .. versionadded:: 1.2
+ """
+ def __init__(self, **options):
+ Filter.__init__(self, **options)
+
+ def filter(self, lexer, stream):
+ current_type = None
+ current_value = None
+ for ttype, value in stream:
+ if ttype is current_type:
+ current_value += value
+ else:
+ if current_type is not None:
+ yield current_type, current_value
+ current_type = ttype
+ current_value = value
+ if current_type is not None:
+ yield current_type, current_value
+
+
+FILTERS = {
+ 'codetagify': CodeTagFilter,
+ 'keywordcase': KeywordCaseFilter,
+ 'highlight': NameHighlightFilter,
+ 'raiseonerror': RaiseOnErrorTokenFilter,
+ 'whitespace': VisibleWhitespaceFilter,
+ 'gobble': GobbleFilter,
+ 'tokenmerge': TokenMergeFilter,
+ 'symbols': SymbolFilter,
+}
diff --git a/src/pip/_vendor/pygments/formatter.py b/src/pip/_vendor/pygments/formatter.py
new file mode 100644
index 000000000..a2349ef86
--- /dev/null
+++ b/src/pip/_vendor/pygments/formatter.py
@@ -0,0 +1,94 @@
+"""
+ pygments.formatter
+ ~~~~~~~~~~~~~~~~~~
+
+ Base formatter class.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import codecs
+
+from pip._vendor.pygments.util import get_bool_opt
+from pip._vendor.pygments.styles import get_style_by_name
+
+__all__ = ['Formatter']
+
+
+def _lookup_style(style):
+ if isinstance(style, str):
+ return get_style_by_name(style)
+ return style
+
+
+class Formatter:
+ """
+ Converts a token stream to text.
+
+ Options accepted:
+
+ ``style``
+ The style to use, can be a string or a Style subclass
+ (default: "default"). Not used by e.g. the
+ TerminalFormatter.
+ ``full``
+ Tells the formatter to output a "full" document, i.e.
+ a complete self-contained document. This doesn't have
+ any effect for some formatters (default: false).
+ ``title``
+ If ``full`` is true, the title that should be used to
+ caption the document (default: '').
+ ``encoding``
+ If given, must be an encoding name. This will be used to
+ convert the Unicode token strings to byte strings in the
+ output. If it is "" or None, Unicode strings will be written
+ to the output file, which most file-like objects do not
+ support (default: None).
+ ``outencoding``
+ Overrides ``encoding`` if given.
+ """
+
+ #: Name of the formatter
+ name = None
+
+ #: Shortcuts for the formatter
+ aliases = []
+
+ #: fn match rules
+ filenames = []
+
+ #: If True, this formatter outputs Unicode strings when no encoding
+ #: option is given.
+ unicodeoutput = True
+
+ def __init__(self, **options):
+ self.style = _lookup_style(options.get('style', 'default'))
+ self.full = get_bool_opt(options, 'full', False)
+ self.title = options.get('title', '')
+ self.encoding = options.get('encoding', None) or None
+ if self.encoding in ('guess', 'chardet'):
+ # can happen for e.g. pygmentize -O encoding=guess
+ self.encoding = 'utf-8'
+ self.encoding = options.get('outencoding') or self.encoding
+ self.options = options
+
+ def get_style_defs(self, arg=''):
+ """
+ Return the style definitions for the current style as a string.
+
+ ``arg`` is an additional argument whose meaning depends on the
+ formatter used. Note that ``arg`` can also be a list or tuple
+ for some formatters like the html formatter.
+ """
+ return ''
+
+ def format(self, tokensource, outfile):
+ """
+ Format ``tokensource``, an iterable of ``(tokentype, tokenstring)``
+ tuples and write it into ``outfile``.
+ """
+ if self.encoding:
+ # wrap the outfile in a StreamWriter
+ outfile = codecs.lookup(self.encoding)[3](outfile)
+ return self.format_unencoded(tokensource, outfile)
diff --git a/src/pip/_vendor/pygments/formatters/__init__.py b/src/pip/_vendor/pygments/formatters/__init__.py
new file mode 100644
index 000000000..43c4c89aa
--- /dev/null
+++ b/src/pip/_vendor/pygments/formatters/__init__.py
@@ -0,0 +1,143 @@
+"""
+ pygments.formatters
+ ~~~~~~~~~~~~~~~~~~~
+
+ Pygments formatters.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import re
+import sys
+import types
+from fnmatch import fnmatch
+from os.path import basename
+
+from pip._vendor.pygments.formatters._mapping import FORMATTERS
+from pip._vendor.pygments.plugin import find_plugin_formatters
+from pip._vendor.pygments.util import ClassNotFound
+
+__all__ = ['get_formatter_by_name', 'get_formatter_for_filename',
+ 'get_all_formatters', 'load_formatter_from_file'] + list(FORMATTERS)
+
+_formatter_cache = {} # classes by name
+
+def _load_formatters(module_name):
+ """Load a formatter (and all others in the module too)."""
+ mod = __import__(module_name, None, None, ['__all__'])
+ for formatter_name in mod.__all__:
+ cls = getattr(mod, formatter_name)
+ _formatter_cache[cls.name] = cls
+
+
+def get_all_formatters():
+ """Return a generator for all formatter classes."""
+ # NB: this returns formatter classes, not info like get_all_lexers().
+ for info in FORMATTERS.values():
+ if info[1] not in _formatter_cache:
+ _load_formatters(info[0])
+ yield _formatter_cache[info[1]]
+ for _, formatter in find_plugin_formatters():
+ yield formatter
+
+
+def find_formatter_class(alias):
+ """Lookup a formatter by alias.
+
+ Returns None if not found.
+ """
+ for module_name, name, aliases, _, _ in FORMATTERS.values():
+ if alias in aliases:
+ if name not in _formatter_cache:
+ _load_formatters(module_name)
+ return _formatter_cache[name]
+ for _, cls in find_plugin_formatters():
+ if alias in cls.aliases:
+ return cls
+
+
+def get_formatter_by_name(_alias, **options):
+ """Lookup and instantiate a formatter by alias.
+
+ Raises ClassNotFound if not found.
+ """
+ cls = find_formatter_class(_alias)
+ if cls is None:
+ raise ClassNotFound("no formatter found for name %r" % _alias)
+ return cls(**options)
+
+
+def load_formatter_from_file(filename, formattername="CustomFormatter",
+ **options):
+ """Load a formatter from a file.
+
+ This method expects a file located relative to the current working
+ directory, which contains a class named CustomFormatter. By default,
+ it expects the Formatter to be named CustomFormatter; you can specify
+ your own class name as the second argument to this function.
+
+ Users should be very careful with the input, because this method
+ is equivalent to running eval on the input file.
+
+ Raises ClassNotFound if there are any problems importing the Formatter.
+
+ .. versionadded:: 2.2
+ """
+ try:
+ # This empty dict will contain the namespace for the exec'd file
+ custom_namespace = {}
+ with open(filename, 'rb') as f:
+ exec(f.read(), custom_namespace)
+ # Retrieve the class `formattername` from that namespace
+ if formattername not in custom_namespace:
+ raise ClassNotFound('no valid %s class found in %s' %
+ (formattername, filename))
+ formatter_class = custom_namespace[formattername]
+ # And finally instantiate it with the options
+ return formatter_class(**options)
+ except OSError as err:
+ raise ClassNotFound('cannot read %s: %s' % (filename, err))
+ except ClassNotFound:
+ raise
+ except Exception as err:
+ raise ClassNotFound('error when loading custom formatter: %s' % err)
+
+
+def get_formatter_for_filename(fn, **options):
+ """Lookup and instantiate a formatter by filename pattern.
+
+ Raises ClassNotFound if not found.
+ """
+ fn = basename(fn)
+ for modname, name, _, filenames, _ in FORMATTERS.values():
+ for filename in filenames:
+ if fnmatch(fn, filename):
+ if name not in _formatter_cache:
+ _load_formatters(modname)
+ return _formatter_cache[name](**options)
+ for cls in find_plugin_formatters():
+ for filename in cls.filenames:
+ if fnmatch(fn, filename):
+ return cls(**options)
+ raise ClassNotFound("no formatter found for file name %r" % fn)
+
+
+class _automodule(types.ModuleType):
+ """Automatically import formatters."""
+
+ def __getattr__(self, name):
+ info = FORMATTERS.get(name)
+ if info:
+ _load_formatters(info[0])
+ cls = _formatter_cache[info[1]]
+ setattr(self, name, cls)
+ return cls
+ raise AttributeError(name)
+
+
+oldmod = sys.modules[__name__]
+newmod = _automodule(__name__)
+newmod.__dict__.update(oldmod.__dict__)
+sys.modules[__name__] = newmod
+del newmod.newmod, newmod.oldmod, newmod.sys, newmod.types
diff --git a/src/pip/_vendor/pygments/formatters/_mapping.py b/src/pip/_vendor/pygments/formatters/_mapping.py
new file mode 100644
index 000000000..6e34f9607
--- /dev/null
+++ b/src/pip/_vendor/pygments/formatters/_mapping.py
@@ -0,0 +1,23 @@
+# Automatically generated by scripts/gen_mapfiles.py.
+# DO NOT EDIT BY HAND; run `make mapfiles` instead.
+
+FORMATTERS = {
+ 'BBCodeFormatter': ('pygments.formatters.bbcode', 'BBCode', ('bbcode', 'bb'), (), 'Format tokens with BBcodes. These formatting codes are used by many bulletin boards, so you can highlight your sourcecode with pygments before posting it there.'),
+ 'BmpImageFormatter': ('pygments.formatters.img', 'img_bmp', ('bmp', 'bitmap'), ('*.bmp',), 'Create a bitmap image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
+ 'GifImageFormatter': ('pygments.formatters.img', 'img_gif', ('gif',), ('*.gif',), 'Create a GIF image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
+ 'GroffFormatter': ('pygments.formatters.groff', 'groff', ('groff', 'troff', 'roff'), (), 'Format tokens with groff escapes to change their color and font style.'),
+ 'HtmlFormatter': ('pygments.formatters.html', 'HTML', ('html',), ('*.html', '*.htm'), "Format tokens as HTML 4 ``<span>`` tags within a ``<pre>`` tag, wrapped in a ``<div>`` tag. The ``<div>``'s CSS class can be set by the `cssclass` option."),
+ 'IRCFormatter': ('pygments.formatters.irc', 'IRC', ('irc', 'IRC'), (), 'Format tokens with IRC color sequences'),
+ 'ImageFormatter': ('pygments.formatters.img', 'img', ('img', 'IMG', 'png'), ('*.png',), 'Create a PNG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
+ 'JpgImageFormatter': ('pygments.formatters.img', 'img_jpg', ('jpg', 'jpeg'), ('*.jpg',), 'Create a JPEG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
+ 'LatexFormatter': ('pygments.formatters.latex', 'LaTeX', ('latex', 'tex'), ('*.tex',), 'Format tokens as LaTeX code. This needs the `fancyvrb` and `color` standard packages.'),
+ 'NullFormatter': ('pygments.formatters.other', 'Text only', ('text', 'null'), ('*.txt',), 'Output the text unchanged without any formatting.'),
+ 'PangoMarkupFormatter': ('pygments.formatters.pangomarkup', 'Pango Markup', ('pango', 'pangomarkup'), (), 'Format tokens as Pango Markup code. It can then be rendered to an SVG.'),
+ 'RawTokenFormatter': ('pygments.formatters.other', 'Raw tokens', ('raw', 'tokens'), ('*.raw',), 'Format tokens as a raw representation for storing token streams.'),
+ 'RtfFormatter': ('pygments.formatters.rtf', 'RTF', ('rtf',), ('*.rtf',), 'Format tokens as RTF markup. This formatter automatically outputs full RTF documents with color information and other useful stuff. Perfect for Copy and Paste into Microsoft(R) Word(R) documents.'),
+ 'SvgFormatter': ('pygments.formatters.svg', 'SVG', ('svg',), ('*.svg',), 'Format tokens as an SVG graphics file. This formatter is still experimental. Each line of code is a ``<text>`` element with explicit ``x`` and ``y`` coordinates containing ``<tspan>`` elements with the individual token styles.'),
+ 'Terminal256Formatter': ('pygments.formatters.terminal256', 'Terminal256', ('terminal256', 'console256', '256'), (), 'Format tokens with ANSI color sequences, for output in a 256-color terminal or console. Like in `TerminalFormatter` color sequences are terminated at newlines, so that paging the output works correctly.'),
+ 'TerminalFormatter': ('pygments.formatters.terminal', 'Terminal', ('terminal', 'console'), (), 'Format tokens with ANSI color sequences, for output in a text console. Color sequences are terminated at newlines, so that paging the output works correctly.'),
+ 'TerminalTrueColorFormatter': ('pygments.formatters.terminal256', 'TerminalTrueColor', ('terminal16m', 'console16m', '16m'), (), 'Format tokens with ANSI color sequences, for output in a true-color terminal or console. Like in `TerminalFormatter` color sequences are terminated at newlines, so that paging the output works correctly.'),
+ 'TestcaseFormatter': ('pygments.formatters.other', 'Testcase', ('testcase',), (), 'Format tokens as appropriate for a new testcase.'),
+}
diff --git a/src/pip/_vendor/pygments/formatters/bbcode.py b/src/pip/_vendor/pygments/formatters/bbcode.py
new file mode 100644
index 000000000..2be2b4e31
--- /dev/null
+++ b/src/pip/_vendor/pygments/formatters/bbcode.py
@@ -0,0 +1,108 @@
+"""
+ pygments.formatters.bbcode
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ BBcode formatter.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.util import get_bool_opt
+
+__all__ = ['BBCodeFormatter']
+
+
+class BBCodeFormatter(Formatter):
+ """
+ Format tokens with BBcodes. These formatting codes are used by many
+ bulletin boards, so you can highlight your sourcecode with pygments before
+ posting it there.
+
+ This formatter has no support for background colors and borders, as there
+ are no common BBcode tags for that.
+
+ Some board systems (e.g. phpBB) don't support colors in their [code] tag,
+ so you can't use the highlighting together with that tag.
+ Text in a [code] tag usually is shown with a monospace font (which this
+ formatter can do with the ``monofont`` option) and no spaces (which you
+ need for indentation) are removed.
+
+ Additional options accepted:
+
+ `style`
+ The style to use, can be a string or a Style subclass (default:
+ ``'default'``).
+
+ `codetag`
+ If set to true, put the output into ``[code]`` tags (default:
+ ``false``)
+
+ `monofont`
+ If set to true, add a tag to show the code with a monospace font
+ (default: ``false``).
+ """
+ name = 'BBCode'
+ aliases = ['bbcode', 'bb']
+ filenames = []
+
+ def __init__(self, **options):
+ Formatter.__init__(self, **options)
+ self._code = get_bool_opt(options, 'codetag', False)
+ self._mono = get_bool_opt(options, 'monofont', False)
+
+ self.styles = {}
+ self._make_styles()
+
+ def _make_styles(self):
+ for ttype, ndef in self.style:
+ start = end = ''
+ if ndef['color']:
+ start += '[color=#%s]' % ndef['color']
+ end = '[/color]' + end
+ if ndef['bold']:
+ start += '[b]'
+ end = '[/b]' + end
+ if ndef['italic']:
+ start += '[i]'
+ end = '[/i]' + end
+ if ndef['underline']:
+ start += '[u]'
+ end = '[/u]' + end
+ # there are no common BBcodes for background-color and border
+
+ self.styles[ttype] = start, end
+
+ def format_unencoded(self, tokensource, outfile):
+ if self._code:
+ outfile.write('[code]')
+ if self._mono:
+ outfile.write('[font=monospace]')
+
+ lastval = ''
+ lasttype = None
+
+ for ttype, value in tokensource:
+ while ttype not in self.styles:
+ ttype = ttype.parent
+ if ttype == lasttype:
+ lastval += value
+ else:
+ if lastval:
+ start, end = self.styles[lasttype]
+ outfile.write(''.join((start, lastval, end)))
+ lastval = value
+ lasttype = ttype
+
+ if lastval:
+ start, end = self.styles[lasttype]
+ outfile.write(''.join((start, lastval, end)))
+
+ if self._mono:
+ outfile.write('[/font]')
+ if self._code:
+ outfile.write('[/code]')
+ if self._code or self._mono:
+ outfile.write('\n')
diff --git a/src/pip/_vendor/pygments/formatters/groff.py b/src/pip/_vendor/pygments/formatters/groff.py
new file mode 100644
index 000000000..f3dcbce9b
--- /dev/null
+++ b/src/pip/_vendor/pygments/formatters/groff.py
@@ -0,0 +1,170 @@
+"""
+ pygments.formatters.groff
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Formatter for groff output.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import math
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.util import get_bool_opt, get_int_opt
+
+__all__ = ['GroffFormatter']
+
+
+class GroffFormatter(Formatter):
+ """
+ Format tokens with groff escapes to change their color and font style.
+
+ .. versionadded:: 2.11
+
+ Additional options accepted:
+
+ `style`
+ The style to use, can be a string or a Style subclass (default:
+ ``'default'``).
+
+ `monospaced`
+ If set to true, monospace font will be used (default: ``true``).
+
+ `linenos`
+ If set to true, print the line numbers (default: ``false``).
+
+ `wrap`
+ Wrap lines to the specified number of characters. Disabled if set to 0
+ (default: ``0``).
+ """
+
+ name = 'groff'
+ aliases = ['groff','troff','roff']
+ filenames = []
+
+ def __init__(self, **options):
+ Formatter.__init__(self, **options)
+
+ self.monospaced = get_bool_opt(options, 'monospaced', True)
+ self.linenos = get_bool_opt(options, 'linenos', False)
+ self._lineno = 0
+ self.wrap = get_int_opt(options, 'wrap', 0)
+ self._linelen = 0
+
+ self.styles = {}
+ self._make_styles()
+
+
+ def _make_styles(self):
+ regular = '\\f[CR]' if self.monospaced else '\\f[R]'
+ bold = '\\f[CB]' if self.monospaced else '\\f[B]'
+ italic = '\\f[CI]' if self.monospaced else '\\f[I]'
+
+ for ttype, ndef in self.style:
+ start = end = ''
+ if ndef['color']:
+ start += '\\m[%s]' % ndef['color']
+ end = '\\m[]' + end
+ if ndef['bold']:
+ start += bold
+ end = regular + end
+ if ndef['italic']:
+ start += italic
+ end = regular + end
+ if ndef['bgcolor']:
+ start += '\\M[%s]' % ndef['bgcolor']
+ end = '\\M[]' + end
+
+ self.styles[ttype] = start, end
+
+
+ def _define_colors(self, outfile):
+ colors = set()
+ for _, ndef in self.style:
+ if ndef['color'] is not None:
+ colors.add(ndef['color'])
+
+ for color in colors:
+ outfile.write('.defcolor ' + color + ' rgb #' + color + '\n')
+
+
+ def _write_lineno(self, outfile):
+ self._lineno += 1
+ outfile.write("%s% 4d " % (self._lineno != 1 and '\n' or '', self._lineno))
+
+
+ def _wrap_line(self, line):
+ length = len(line.rstrip('\n'))
+ space = ' ' if self.linenos else ''
+ newline = ''
+
+ if length > self.wrap:
+ for i in range(0, math.floor(length / self.wrap)):
+ chunk = line[i*self.wrap:i*self.wrap+self.wrap]
+ newline += (chunk + '\n' + space)
+ remainder = length % self.wrap
+ if remainder > 0:
+ newline += line[-remainder-1:]
+ self._linelen = remainder
+ elif self._linelen + length > self.wrap:
+ newline = ('\n' + space) + line
+ self._linelen = length
+ else:
+ newline = line
+ self._linelen += length
+
+ return newline
+
+
+ def _escape_chars(self, text):
+ text = text.replace('\\', '\\[u005C]'). \
+ replace('.', '\\[char46]'). \
+ replace('\'', '\\[u0027]'). \
+ replace('`', '\\[u0060]'). \
+ replace('~', '\\[u007E]')
+ copy = text
+
+ for char in copy:
+ if len(char) != len(char.encode()):
+ uni = char.encode('unicode_escape') \
+ .decode()[1:] \
+ .replace('x', 'u00') \
+ .upper()
+ text = text.replace(char, '\\[u' + uni[1:] + ']')
+
+ return text
+
+
+ def format_unencoded(self, tokensource, outfile):
+ self._define_colors(outfile)
+
+ outfile.write('.nf\n\\f[CR]\n')
+
+ if self.linenos:
+ self._write_lineno(outfile)
+
+ for ttype, value in tokensource:
+ while ttype not in self.styles:
+ ttype = ttype.parent
+ start, end = self.styles[ttype]
+
+ for line in value.splitlines(True):
+ if self.wrap > 0:
+ line = self._wrap_line(line)
+
+ if start and end:
+ text = self._escape_chars(line.rstrip('\n'))
+ if text != '':
+ outfile.write(''.join((start, text, end)))
+ else:
+ outfile.write(self._escape_chars(line.rstrip('\n')))
+
+ if line.endswith('\n'):
+ if self.linenos:
+ self._write_lineno(outfile)
+ self._linelen = 0
+ else:
+ outfile.write('\n')
+ self._linelen = 0
+
+ outfile.write('\n.fi')
diff --git a/src/pip/_vendor/pygments/formatters/html.py b/src/pip/_vendor/pygments/formatters/html.py
new file mode 100644
index 000000000..d5cda4c4b
--- /dev/null
+++ b/src/pip/_vendor/pygments/formatters/html.py
@@ -0,0 +1,989 @@
+"""
+ pygments.formatters.html
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Formatter for HTML output.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import functools
+import os
+import sys
+import os.path
+from io import StringIO
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.token import Token, Text, STANDARD_TYPES
+from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt
+
+try:
+ import ctags
+except ImportError:
+ ctags = None
+
+__all__ = ['HtmlFormatter']
+
+
+_escape_html_table = {
+ ord('&'): '&amp;',
+ ord('<'): '&lt;',
+ ord('>'): '&gt;',
+ ord('"'): '&quot;',
+ ord("'"): '&#39;',
+}
+
+
+def escape_html(text, table=_escape_html_table):
+ """Escape &, <, > as well as single and double quotes for HTML."""
+ return text.translate(table)
+
+
+def webify(color):
+ if color.startswith('calc') or color.startswith('var'):
+ return color
+ else:
+ return '#' + color
+
+
+def _get_ttype_class(ttype):
+ fname = STANDARD_TYPES.get(ttype)
+ if fname:
+ return fname
+ aname = ''
+ while fname is None:
+ aname = '-' + ttype[-1] + aname
+ ttype = ttype.parent
+ fname = STANDARD_TYPES.get(ttype)
+ return fname + aname
+
+
+CSSFILE_TEMPLATE = '''\
+/*
+generated by Pygments <https://pygments.org/>
+Copyright 2006-2022 by the Pygments team.
+Licensed under the BSD license, see LICENSE for details.
+*/
+%(styledefs)s
+'''
+
+DOC_HEADER = '''\
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<!--
+generated by Pygments <https://pygments.org/>
+Copyright 2006-2022 by the Pygments team.
+Licensed under the BSD license, see LICENSE for details.
+-->
+<html>
+<head>
+ <title>%(title)s</title>
+ <meta http-equiv="content-type" content="text/html; charset=%(encoding)s">
+ <style type="text/css">
+''' + CSSFILE_TEMPLATE + '''
+ </style>
+</head>
+<body>
+<h2>%(title)s</h2>
+
+'''
+
+DOC_HEADER_EXTERNALCSS = '''\
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+
+<html>
+<head>
+ <title>%(title)s</title>
+ <meta http-equiv="content-type" content="text/html; charset=%(encoding)s">
+ <link rel="stylesheet" href="%(cssfile)s" type="text/css">
+</head>
+<body>
+<h2>%(title)s</h2>
+
+'''
+
+DOC_FOOTER = '''\
+</body>
+</html>
+'''
+
+
+class HtmlFormatter(Formatter):
+ r"""
+ Format tokens as HTML 4 ``<span>`` tags within a ``<pre>`` tag, wrapped
+ in a ``<div>`` tag. The ``<div>``'s CSS class can be set by the `cssclass`
+ option.
+
+ If the `linenos` option is set to ``"table"``, the ``<pre>`` is
+ additionally wrapped inside a ``<table>`` which has one row and two
+ cells: one containing the line numbers and one containing the code.
+ Example:
+
+ .. sourcecode:: html
+
+ <div class="highlight" >
+ <table><tr>
+ <td class="linenos" title="click to toggle"
+ onclick="with (this.firstChild.style)
+ { display = (display == '') ? 'none' : '' }">
+ <pre>1
+ 2</pre>
+ </td>
+ <td class="code">
+ <pre><span class="Ke">def </span><span class="NaFu">foo</span>(bar):
+ <span class="Ke">pass</span>
+ </pre>
+ </td>
+ </tr></table></div>
+
+ (whitespace added to improve clarity).
+
+ Wrapping can be disabled using the `nowrap` option.
+
+ A list of lines can be specified using the `hl_lines` option to make these
+ lines highlighted (as of Pygments 0.11).
+
+ With the `full` option, a complete HTML 4 document is output, including
+ the style definitions inside a ``<style>`` tag, or in a separate file if
+ the `cssfile` option is given.
+
+ When `tagsfile` is set to the path of a ctags index file, it is used to
+ generate hyperlinks from names to their definition. You must enable
+ `lineanchors` and run ctags with the `-n` option for this to work. The
+ `python-ctags` module from PyPI must be installed to use this feature;
+ otherwise a `RuntimeError` will be raised.
+
+ The `get_style_defs(arg='')` method of a `HtmlFormatter` returns a string
+ containing CSS rules for the CSS classes used by the formatter. The
+ argument `arg` can be used to specify additional CSS selectors that
+ are prepended to the classes. A call `fmter.get_style_defs('td .code')`
+ would result in the following CSS classes:
+
+ .. sourcecode:: css
+
+ td .code .kw { font-weight: bold; color: #00FF00 }
+ td .code .cm { color: #999999 }
+ ...
+
+ If you have Pygments 0.6 or higher, you can also pass a list or tuple to the
+ `get_style_defs()` method to request multiple prefixes for the tokens:
+
+ .. sourcecode:: python
+
+ formatter.get_style_defs(['div.syntax pre', 'pre.syntax'])
+
+ The output would then look like this:
+
+ .. sourcecode:: css
+
+ div.syntax pre .kw,
+ pre.syntax .kw { font-weight: bold; color: #00FF00 }
+ div.syntax pre .cm,
+ pre.syntax .cm { color: #999999 }
+ ...
+
+ Additional options accepted:
+
+ `nowrap`
+ If set to ``True``, don't wrap the tokens at all, not even inside a ``<pre>``
+ tag. This disables most other options (default: ``False``).
+
+ `full`
+ Tells the formatter to output a "full" document, i.e. a complete
+ self-contained document (default: ``False``).
+
+ `title`
+ If `full` is true, the title that should be used to caption the
+ document (default: ``''``).
+
+ `style`
+ The style to use, can be a string or a Style subclass (default:
+ ``'default'``). This option has no effect if the `cssfile`
+ and `noclobber_cssfile` option are given and the file specified in
+ `cssfile` exists.
+
+ `noclasses`
+ If set to true, token ``<span>`` tags (as well as line number elements)
+ will not use CSS classes, but inline styles. This is not recommended
+ for larger pieces of code since it increases output size by quite a bit
+ (default: ``False``).
+
+ `classprefix`
+ Since the token types use relatively short class names, they may clash
+ with some of your own class names. In this case you can use the
+ `classprefix` option to give a string to prepend to all Pygments-generated
+ CSS class names for token types.
+ Note that this option also affects the output of `get_style_defs()`.
+
+ `cssclass`
+ CSS class for the wrapping ``<div>`` tag (default: ``'highlight'``).
+ If you set this option, the default selector for `get_style_defs()`
+ will be this class.
+
+ .. versionadded:: 0.9
+ If you select the ``'table'`` line numbers, the wrapping table will
+ have a CSS class of this string plus ``'table'``, the default is
+ accordingly ``'highlighttable'``.
+
+ `cssstyles`
+ Inline CSS styles for the wrapping ``<div>`` tag (default: ``''``).
+
+ `prestyles`
+ Inline CSS styles for the ``<pre>`` tag (default: ``''``).
+
+ .. versionadded:: 0.11
+
+ `cssfile`
+ If the `full` option is true and this option is given, it must be the
+ name of an external file. If the filename does not include an absolute
+ path, the file's path will be assumed to be relative to the main output
+ file's path, if the latter can be found. The stylesheet is then written
+ to this file instead of the HTML file.
+
+ .. versionadded:: 0.6
+
+ `noclobber_cssfile`
+ If `cssfile` is given and the specified file exists, the css file will
+ not be overwritten. This allows the use of the `full` option in
+ combination with a user specified css file. Default is ``False``.
+
+ .. versionadded:: 1.1
+
+ `linenos`
+ If set to ``'table'``, output line numbers as a table with two cells,
+ one containing the line numbers, the other the whole code. This is
+ copy-and-paste-friendly, but may cause alignment problems with some
+ browsers or fonts. If set to ``'inline'``, the line numbers will be
+ integrated in the ``<pre>`` tag that contains the code (that setting
+ is *new in Pygments 0.8*).
+
+ For compatibility with Pygments 0.7 and earlier, every true value
+ except ``'inline'`` means the same as ``'table'`` (in particular, that
+ means also ``True``).
+
+ The default value is ``False``, which means no line numbers at all.
+
+ **Note:** with the default ("table") line number mechanism, the line
+ numbers and code can have different line heights in Internet Explorer
+ unless you give the enclosing ``<pre>`` tags an explicit ``line-height``
+ CSS property (you get the default line spacing with ``line-height:
+ 125%``).
+
+ `hl_lines`
+ Specify a list of lines to be highlighted. The line numbers are always
+ relative to the input (i.e. the first line is line 1) and are
+ independent of `linenostart`.
+
+ .. versionadded:: 0.11
+
+ `linenostart`
+ The line number for the first line (default: ``1``).
+
+ `linenostep`
+ If set to a number n > 1, only every nth line number is printed.
+
+ `linenospecial`
+ If set to a number n > 0, every nth line number is given the CSS
+ class ``"special"`` (default: ``0``).
+
+ `nobackground`
+ If set to ``True``, the formatter won't output the background color
+ for the wrapping element (this automatically defaults to ``False``
+ when there is no wrapping element [eg: no argument for the
+ `get_syntax_defs` method given]) (default: ``False``).
+
+ .. versionadded:: 0.6
+
+ `lineseparator`
+ This string is output between lines of code. It defaults to ``"\n"``,
+ which is enough to break a line inside ``<pre>`` tags, but you can
+ e.g. set it to ``"<br>"`` to get HTML line breaks.
+
+ .. versionadded:: 0.7
+
+ `lineanchors`
+ If set to a nonempty string, e.g. ``foo``, the formatter will wrap each
+ output line in an anchor tag with an ``id`` (and `name`) of ``foo-linenumber``.
+ This allows easy linking to certain lines.
+
+ .. versionadded:: 0.9
+
+ `linespans`
+ If set to a nonempty string, e.g. ``foo``, the formatter will wrap each
+ output line in a span tag with an ``id`` of ``foo-linenumber``.
+ This allows easy access to lines via javascript.
+
+ .. versionadded:: 1.6
+
+ `anchorlinenos`
+ If set to `True`, will wrap line numbers in <a> tags. Used in
+ combination with `linenos` and `lineanchors`.
+
+ `tagsfile`
+ If set to the path of a ctags file, wrap names in anchor tags that
+ link to their definitions. `lineanchors` should be used, and the
+ tags file should specify line numbers (see the `-n` option to ctags).
+
+ .. versionadded:: 1.6
+
+ `tagurlformat`
+ A string formatting pattern used to generate links to ctags definitions.
+ Available variables are `%(path)s`, `%(fname)s` and `%(fext)s`.
+ Defaults to an empty string, resulting in just `#prefix-number` links.
+
+ .. versionadded:: 1.6
+
+ `filename`
+ A string used to generate a filename when rendering ``<pre>`` blocks,
+ for example if displaying source code. If `linenos` is set to
+ ``'table'`` then the filename will be rendered in an initial row
+ containing a single `<th>` which spans both columns.
+
+ .. versionadded:: 2.1
+
+ `wrapcode`
+ Wrap the code inside ``<pre>`` blocks using ``<code>``, as recommended
+ by the HTML5 specification.
+
+ .. versionadded:: 2.4
+
+ `debug_token_types`
+ Add ``title`` attributes to all token ``<span>`` tags that show the
+ name of the token.
+
+ .. versionadded:: 2.10
+
+
+ **Subclassing the HTML formatter**
+
+ .. versionadded:: 0.7
+
+ The HTML formatter is now built in a way that allows easy subclassing, thus
+ customizing the output HTML code. The `format()` method calls
+ `self._format_lines()` which returns a generator that yields tuples of ``(1,
+ line)``, where the ``1`` indicates that the ``line`` is a line of the
+ formatted source code.
+
+ If the `nowrap` option is set, the generator is the iterated over and the
+ resulting HTML is output.
+
+ Otherwise, `format()` calls `self.wrap()`, which wraps the generator with
+ other generators. These may add some HTML code to the one generated by
+ `_format_lines()`, either by modifying the lines generated by the latter,
+ then yielding them again with ``(1, line)``, and/or by yielding other HTML
+ code before or after the lines, with ``(0, html)``. The distinction between
+ source lines and other code makes it possible to wrap the generator multiple
+ times.
+
+ The default `wrap()` implementation adds a ``<div>`` and a ``<pre>`` tag.
+
+ A custom `HtmlFormatter` subclass could look like this:
+
+ .. sourcecode:: python
+
+ class CodeHtmlFormatter(HtmlFormatter):
+
+ def wrap(self, source, *, include_div):
+ return self._wrap_code(source)
+
+ def _wrap_code(self, source):
+ yield 0, '<code>'
+ for i, t in source:
+ if i == 1:
+ # it's a line of formatted code
+ t += '<br>'
+ yield i, t
+ yield 0, '</code>'
+
+ This results in wrapping the formatted lines with a ``<code>`` tag, where the
+ source lines are broken using ``<br>`` tags.
+
+ After calling `wrap()`, the `format()` method also adds the "line numbers"
+ and/or "full document" wrappers if the respective options are set. Then, all
+ HTML yielded by the wrapped generator is output.
+ """
+
+ name = 'HTML'
+ aliases = ['html']
+ filenames = ['*.html', '*.htm']
+
+ def __init__(self, **options):
+ Formatter.__init__(self, **options)
+ self.title = self._decodeifneeded(self.title)
+ self.nowrap = get_bool_opt(options, 'nowrap', False)
+ self.noclasses = get_bool_opt(options, 'noclasses', False)
+ self.classprefix = options.get('classprefix', '')
+ self.cssclass = self._decodeifneeded(options.get('cssclass', 'highlight'))
+ self.cssstyles = self._decodeifneeded(options.get('cssstyles', ''))
+ self.prestyles = self._decodeifneeded(options.get('prestyles', ''))
+ self.cssfile = self._decodeifneeded(options.get('cssfile', ''))
+ self.noclobber_cssfile = get_bool_opt(options, 'noclobber_cssfile', False)
+ self.tagsfile = self._decodeifneeded(options.get('tagsfile', ''))
+ self.tagurlformat = self._decodeifneeded(options.get('tagurlformat', ''))
+ self.filename = self._decodeifneeded(options.get('filename', ''))
+ self.wrapcode = get_bool_opt(options, 'wrapcode', False)
+ self.span_element_openers = {}
+ self.debug_token_types = get_bool_opt(options, 'debug_token_types', False)
+
+ if self.tagsfile:
+ if not ctags:
+ raise RuntimeError('The "ctags" package must to be installed '
+ 'to be able to use the "tagsfile" feature.')
+ self._ctags = ctags.CTags(self.tagsfile)
+
+ linenos = options.get('linenos', False)
+ if linenos == 'inline':
+ self.linenos = 2
+ elif linenos:
+ # compatibility with <= 0.7
+ self.linenos = 1
+ else:
+ self.linenos = 0
+ self.linenostart = abs(get_int_opt(options, 'linenostart', 1))
+ self.linenostep = abs(get_int_opt(options, 'linenostep', 1))
+ self.linenospecial = abs(get_int_opt(options, 'linenospecial', 0))
+ self.nobackground = get_bool_opt(options, 'nobackground', False)
+ self.lineseparator = options.get('lineseparator', '\n')
+ self.lineanchors = options.get('lineanchors', '')
+ self.linespans = options.get('linespans', '')
+ self.anchorlinenos = get_bool_opt(options, 'anchorlinenos', False)
+ self.hl_lines = set()
+ for lineno in get_list_opt(options, 'hl_lines', []):
+ try:
+ self.hl_lines.add(int(lineno))
+ except ValueError:
+ pass
+
+ self._create_stylesheet()
+
+ def _get_css_class(self, ttype):
+ """Return the css class of this token type prefixed with
+ the classprefix option."""
+ ttypeclass = _get_ttype_class(ttype)
+ if ttypeclass:
+ return self.classprefix + ttypeclass
+ return ''
+
+ def _get_css_classes(self, ttype):
+ """Return the CSS classes of this token type prefixed with the classprefix option."""
+ cls = self._get_css_class(ttype)
+ while ttype not in STANDARD_TYPES:
+ ttype = ttype.parent
+ cls = self._get_css_class(ttype) + ' ' + cls
+ return cls or ''
+
+ def _get_css_inline_styles(self, ttype):
+ """Return the inline CSS styles for this token type."""
+ cclass = self.ttype2class.get(ttype)
+ while cclass is None:
+ ttype = ttype.parent
+ cclass = self.ttype2class.get(ttype)
+ return cclass or ''
+
+ def _create_stylesheet(self):
+ t2c = self.ttype2class = {Token: ''}
+ c2s = self.class2style = {}
+ for ttype, ndef in self.style:
+ name = self._get_css_class(ttype)
+ style = ''
+ if ndef['color']:
+ style += 'color: %s; ' % webify(ndef['color'])
+ if ndef['bold']:
+ style += 'font-weight: bold; '
+ if ndef['italic']:
+ style += 'font-style: italic; '
+ if ndef['underline']:
+ style += 'text-decoration: underline; '
+ if ndef['bgcolor']:
+ style += 'background-color: %s; ' % webify(ndef['bgcolor'])
+ if ndef['border']:
+ style += 'border: 1px solid %s; ' % webify(ndef['border'])
+ if style:
+ t2c[ttype] = name
+ # save len(ttype) to enable ordering the styles by
+ # hierarchy (necessary for CSS cascading rules!)
+ c2s[name] = (style[:-2], ttype, len(ttype))
+
+ def get_style_defs(self, arg=None):
+ """
+ Return CSS style definitions for the classes produced by the current
+ highlighting style. ``arg`` can be a string or list of selectors to
+ insert before the token type classes.
+ """
+ style_lines = []
+
+ style_lines.extend(self.get_linenos_style_defs())
+ style_lines.extend(self.get_background_style_defs(arg))
+ style_lines.extend(self.get_token_style_defs(arg))
+
+ return '\n'.join(style_lines)
+
+ def get_token_style_defs(self, arg=None):
+ prefix = self.get_css_prefix(arg)
+
+ styles = [
+ (level, ttype, cls, style)
+ for cls, (style, ttype, level) in self.class2style.items()
+ if cls and style
+ ]
+ styles.sort()
+
+ lines = [
+ '%s { %s } /* %s */' % (prefix(cls), style, repr(ttype)[6:])
+ for (level, ttype, cls, style) in styles
+ ]
+
+ return lines
+
+ def get_background_style_defs(self, arg=None):
+ prefix = self.get_css_prefix(arg)
+ bg_color = self.style.background_color
+ hl_color = self.style.highlight_color
+
+ lines = []
+
+ if arg and not self.nobackground and bg_color is not None:
+ text_style = ''
+ if Text in self.ttype2class:
+ text_style = ' ' + self.class2style[self.ttype2class[Text]][0]
+ lines.insert(
+ 0, '%s{ background: %s;%s }' % (
+ prefix(''), bg_color, text_style
+ )
+ )
+ if hl_color is not None:
+ lines.insert(
+ 0, '%s { background-color: %s }' % (prefix('hll'), hl_color)
+ )
+
+ return lines
+
+ def get_linenos_style_defs(self):
+ lines = [
+ 'pre { %s }' % self._pre_style,
+ 'td.linenos .normal { %s }' % self._linenos_style,
+ 'span.linenos { %s }' % self._linenos_style,
+ 'td.linenos .special { %s }' % self._linenos_special_style,
+ 'span.linenos.special { %s }' % self._linenos_special_style,
+ ]
+
+ return lines
+
+ def get_css_prefix(self, arg):
+ if arg is None:
+ arg = ('cssclass' in self.options and '.'+self.cssclass or '')
+ if isinstance(arg, str):
+ args = [arg]
+ else:
+ args = list(arg)
+
+ def prefix(cls):
+ if cls:
+ cls = '.' + cls
+ tmp = []
+ for arg in args:
+ tmp.append((arg and arg + ' ' or '') + cls)
+ return ', '.join(tmp)
+
+ return prefix
+
+ @property
+ def _pre_style(self):
+ return 'line-height: 125%;'
+
+ @property
+ def _linenos_style(self):
+ return 'color: %s; background-color: %s; padding-left: 5px; padding-right: 5px;' % (
+ self.style.line_number_color,
+ self.style.line_number_background_color
+ )
+
+ @property
+ def _linenos_special_style(self):
+ return 'color: %s; background-color: %s; padding-left: 5px; padding-right: 5px;' % (
+ self.style.line_number_special_color,
+ self.style.line_number_special_background_color
+ )
+
+ def _decodeifneeded(self, value):
+ if isinstance(value, bytes):
+ if self.encoding:
+ return value.decode(self.encoding)
+ return value.decode()
+ return value
+
+ def _wrap_full(self, inner, outfile):
+ if self.cssfile:
+ if os.path.isabs(self.cssfile):
+ # it's an absolute filename
+ cssfilename = self.cssfile
+ else:
+ try:
+ filename = outfile.name
+ if not filename or filename[0] == '<':
+ # pseudo files, e.g. name == '<fdopen>'
+ raise AttributeError
+ cssfilename = os.path.join(os.path.dirname(filename),
+ self.cssfile)
+ except AttributeError:
+ print('Note: Cannot determine output file name, '
+ 'using current directory as base for the CSS file name',
+ file=sys.stderr)
+ cssfilename = self.cssfile
+ # write CSS file only if noclobber_cssfile isn't given as an option.
+ try:
+ if not os.path.exists(cssfilename) or not self.noclobber_cssfile:
+ with open(cssfilename, "w") as cf:
+ cf.write(CSSFILE_TEMPLATE %
+ {'styledefs': self.get_style_defs('body')})
+ except OSError as err:
+ err.strerror = 'Error writing CSS file: ' + err.strerror
+ raise
+
+ yield 0, (DOC_HEADER_EXTERNALCSS %
+ dict(title=self.title,
+ cssfile=self.cssfile,
+ encoding=self.encoding))
+ else:
+ yield 0, (DOC_HEADER %
+ dict(title=self.title,
+ styledefs=self.get_style_defs('body'),
+ encoding=self.encoding))
+
+ yield from inner
+ yield 0, DOC_FOOTER
+
+ def _wrap_tablelinenos(self, inner):
+ dummyoutfile = StringIO()
+ lncount = 0
+ for t, line in inner:
+ if t:
+ lncount += 1
+ dummyoutfile.write(line)
+
+ fl = self.linenostart
+ mw = len(str(lncount + fl - 1))
+ sp = self.linenospecial
+ st = self.linenostep
+ anchor_name = self.lineanchors or self.linespans
+ aln = self.anchorlinenos
+ nocls = self.noclasses
+
+ lines = []
+
+ for i in range(fl, fl+lncount):
+ print_line = i % st == 0
+ special_line = sp and i % sp == 0
+
+ if print_line:
+ line = '%*d' % (mw, i)
+ if aln:
+ line = '<a href="#%s-%d">%s</a>' % (anchor_name, i, line)
+ else:
+ line = ' ' * mw
+
+ if nocls:
+ if special_line:
+ style = ' style="%s"' % self._linenos_special_style
+ else:
+ style = ' style="%s"' % self._linenos_style
+ else:
+ if special_line:
+ style = ' class="special"'
+ else:
+ style = ' class="normal"'
+
+ if style:
+ line = '<span%s>%s</span>' % (style, line)
+
+ lines.append(line)
+
+ ls = '\n'.join(lines)
+
+ # If a filename was specified, we can't put it into the code table as it
+ # would misalign the line numbers. Hence we emit a separate row for it.
+ filename_tr = ""
+ if self.filename:
+ filename_tr = (
+ '<tr><th colspan="2" class="filename">'
+ '<span class="filename">' + self.filename + '</span>'
+ '</th></tr>')
+
+ # in case you wonder about the seemingly redundant <div> here: since the
+ # content in the other cell also is wrapped in a div, some browsers in
+ # some configurations seem to mess up the formatting...
+ yield 0, (f'<table class="{self.cssclass}table">' + filename_tr +
+ '<tr><td class="linenos"><div class="linenodiv"><pre>' +
+ ls + '</pre></div></td><td class="code">')
+ yield 0, '<div>'
+ yield 0, dummyoutfile.getvalue()
+ yield 0, '</div>'
+ yield 0, '</td></tr></table>'
+
+
+ def _wrap_inlinelinenos(self, inner):
+ # need a list of lines since we need the width of a single number :(
+ inner_lines = list(inner)
+ sp = self.linenospecial
+ st = self.linenostep
+ num = self.linenostart
+ mw = len(str(len(inner_lines) + num - 1))
+ anchor_name = self.lineanchors or self.linespans
+ aln = self.anchorlinenos
+ nocls = self.noclasses
+
+ for _, inner_line in inner_lines:
+ print_line = num % st == 0
+ special_line = sp and num % sp == 0
+
+ if print_line:
+ line = '%*d' % (mw, num)
+ else:
+ line = ' ' * mw
+
+ if nocls:
+ if special_line:
+ style = ' style="%s"' % self._linenos_special_style
+ else:
+ style = ' style="%s"' % self._linenos_style
+ else:
+ if special_line:
+ style = ' class="linenos special"'
+ else:
+ style = ' class="linenos"'
+
+ if style:
+ linenos = '<span%s>%s</span>' % (style, line)
+ else:
+ linenos = line
+
+ if aln:
+ yield 1, ('<a href="#%s-%d">%s</a>' % (anchor_name, num, linenos) +
+ inner_line)
+ else:
+ yield 1, linenos + inner_line
+ num += 1
+
+ def _wrap_lineanchors(self, inner):
+ s = self.lineanchors
+ # subtract 1 since we have to increment i *before* yielding
+ i = self.linenostart - 1
+ for t, line in inner:
+ if t:
+ i += 1
+ href = "" if self.linenos else ' href="#%s-%d"' % (s, i)
+ yield 1, '<a id="%s-%d" name="%s-%d"%s></a>' % (s, i, s, i, href) + line
+ else:
+ yield 0, line
+
+ def _wrap_linespans(self, inner):
+ s = self.linespans
+ i = self.linenostart - 1
+ for t, line in inner:
+ if t:
+ i += 1
+ yield 1, '<span id="%s-%d">%s</span>' % (s, i, line)
+ else:
+ yield 0, line
+
+ def _wrap_div(self, inner):
+ style = []
+ if (self.noclasses and not self.nobackground and
+ self.style.background_color is not None):
+ style.append('background: %s' % (self.style.background_color,))
+ if self.cssstyles:
+ style.append(self.cssstyles)
+ style = '; '.join(style)
+
+ yield 0, ('<div' + (self.cssclass and ' class="%s"' % self.cssclass) +
+ (style and (' style="%s"' % style)) + '>')
+ yield from inner
+ yield 0, '</div>\n'
+
+ def _wrap_pre(self, inner):
+ style = []
+ if self.prestyles:
+ style.append(self.prestyles)
+ if self.noclasses:
+ style.append(self._pre_style)
+ style = '; '.join(style)
+
+ if self.filename and self.linenos != 1:
+ yield 0, ('<span class="filename">' + self.filename + '</span>')
+
+ # the empty span here is to keep leading empty lines from being
+ # ignored by HTML parsers
+ yield 0, ('<pre' + (style and ' style="%s"' % style) + '><span></span>')
+ yield from inner
+ yield 0, '</pre>'
+
+ def _wrap_code(self, inner):
+ yield 0, '<code>'
+ yield from inner
+ yield 0, '</code>'
+
+ @functools.lru_cache(maxsize=100)
+ def _translate_parts(self, value):
+ """HTML-escape a value and split it by newlines."""
+ return value.translate(_escape_html_table).split('\n')
+
+ def _format_lines(self, tokensource):
+ """
+ Just format the tokens, without any wrapping tags.
+ Yield individual lines.
+ """
+ nocls = self.noclasses
+ lsep = self.lineseparator
+ tagsfile = self.tagsfile
+
+ lspan = ''
+ line = []
+ for ttype, value in tokensource:
+ try:
+ cspan = self.span_element_openers[ttype]
+ except KeyError:
+ title = ' title="%s"' % '.'.join(ttype) if self.debug_token_types else ''
+ if nocls:
+ css_style = self._get_css_inline_styles(ttype)
+ if css_style:
+ css_style = self.class2style[css_style][0]
+ cspan = '<span style="%s"%s>' % (css_style, title)
+ else:
+ cspan = ''
+ else:
+ css_class = self._get_css_classes(ttype)
+ if css_class:
+ cspan = '<span class="%s"%s>' % (css_class, title)
+ else:
+ cspan = ''
+ self.span_element_openers[ttype] = cspan
+
+ parts = self._translate_parts(value)
+
+ if tagsfile and ttype in Token.Name:
+ filename, linenumber = self._lookup_ctag(value)
+ if linenumber:
+ base, filename = os.path.split(filename)
+ if base:
+ base += '/'
+ filename, extension = os.path.splitext(filename)
+ url = self.tagurlformat % {'path': base, 'fname': filename,
+ 'fext': extension}
+ parts[0] = "<a href=\"%s#%s-%d\">%s" % \
+ (url, self.lineanchors, linenumber, parts[0])
+ parts[-1] = parts[-1] + "</a>"
+
+ # for all but the last line
+ for part in parts[:-1]:
+ if line:
+ if lspan != cspan:
+ line.extend(((lspan and '</span>'), cspan, part,
+ (cspan and '</span>'), lsep))
+ else: # both are the same
+ line.extend((part, (lspan and '</span>'), lsep))
+ yield 1, ''.join(line)
+ line = []
+ elif part:
+ yield 1, ''.join((cspan, part, (cspan and '</span>'), lsep))
+ else:
+ yield 1, lsep
+ # for the last line
+ if line and parts[-1]:
+ if lspan != cspan:
+ line.extend(((lspan and '</span>'), cspan, parts[-1]))
+ lspan = cspan
+ else:
+ line.append(parts[-1])
+ elif parts[-1]:
+ line = [cspan, parts[-1]]
+ lspan = cspan
+ # else we neither have to open a new span nor set lspan
+
+ if line:
+ line.extend(((lspan and '</span>'), lsep))
+ yield 1, ''.join(line)
+
+ def _lookup_ctag(self, token):
+ entry = ctags.TagEntry()
+ if self._ctags.find(entry, token.encode(), 0):
+ return entry['file'], entry['lineNumber']
+ else:
+ return None, None
+
+ def _highlight_lines(self, tokensource):
+ """
+ Highlighted the lines specified in the `hl_lines` option by
+ post-processing the token stream coming from `_format_lines`.
+ """
+ hls = self.hl_lines
+
+ for i, (t, value) in enumerate(tokensource):
+ if t != 1:
+ yield t, value
+ if i + 1 in hls: # i + 1 because Python indexes start at 0
+ if self.noclasses:
+ style = ''
+ if self.style.highlight_color is not None:
+ style = (' style="background-color: %s"' %
+ (self.style.highlight_color,))
+ yield 1, '<span%s>%s</span>' % (style, value)
+ else:
+ yield 1, '<span class="hll">%s</span>' % value
+ else:
+ yield 1, value
+
+ def wrap(self, source):
+ """
+ Wrap the ``source``, which is a generator yielding
+ individual lines, in custom generators. See docstring
+ for `format`. Can be overridden.
+ """
+
+ output = source
+ if self.wrapcode:
+ output = self._wrap_code(output)
+
+ output = self._wrap_pre(output)
+
+ return output
+
+ def format_unencoded(self, tokensource, outfile):
+ """
+ The formatting process uses several nested generators; which of
+ them are used is determined by the user's options.
+
+ Each generator should take at least one argument, ``inner``,
+ and wrap the pieces of text generated by this.
+
+ Always yield 2-tuples: (code, text). If "code" is 1, the text
+ is part of the original tokensource being highlighted, if it's
+ 0, the text is some piece of wrapping. This makes it possible to
+ use several different wrappers that process the original source
+ linewise, e.g. line number generators.
+ """
+ source = self._format_lines(tokensource)
+
+ # As a special case, we wrap line numbers before line highlighting
+ # so the line numbers get wrapped in the highlighting tag.
+ if not self.nowrap and self.linenos == 2:
+ source = self._wrap_inlinelinenos(source)
+
+ if self.hl_lines:
+ source = self._highlight_lines(source)
+
+ if not self.nowrap:
+ if self.lineanchors:
+ source = self._wrap_lineanchors(source)
+ if self.linespans:
+ source = self._wrap_linespans(source)
+ source = self.wrap(source)
+ if self.linenos == 1:
+ source = self._wrap_tablelinenos(source)
+ source = self._wrap_div(source)
+ if self.full:
+ source = self._wrap_full(source, outfile)
+
+ for t, piece in source:
+ outfile.write(piece)
diff --git a/src/pip/_vendor/pygments/formatters/img.py b/src/pip/_vendor/pygments/formatters/img.py
new file mode 100644
index 000000000..0f36a32ba
--- /dev/null
+++ b/src/pip/_vendor/pygments/formatters/img.py
@@ -0,0 +1,645 @@
+"""
+ pygments.formatters.img
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+ Formatter for Pixmap output.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import os
+import sys
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt, \
+ get_choice_opt
+
+import subprocess
+
+# Import this carefully
+try:
+ from PIL import Image, ImageDraw, ImageFont
+ pil_available = True
+except ImportError:
+ pil_available = False
+
+try:
+ import _winreg
+except ImportError:
+ try:
+ import winreg as _winreg
+ except ImportError:
+ _winreg = None
+
+__all__ = ['ImageFormatter', 'GifImageFormatter', 'JpgImageFormatter',
+ 'BmpImageFormatter']
+
+
+# For some unknown reason every font calls it something different
+STYLES = {
+ 'NORMAL': ['', 'Roman', 'Book', 'Normal', 'Regular', 'Medium'],
+ 'ITALIC': ['Oblique', 'Italic'],
+ 'BOLD': ['Bold'],
+ 'BOLDITALIC': ['Bold Oblique', 'Bold Italic'],
+}
+
+# A sane default for modern systems
+DEFAULT_FONT_NAME_NIX = 'DejaVu Sans Mono'
+DEFAULT_FONT_NAME_WIN = 'Courier New'
+DEFAULT_FONT_NAME_MAC = 'Menlo'
+
+
+class PilNotAvailable(ImportError):
+ """When Python imaging library is not available"""
+
+
+class FontNotFound(Exception):
+ """When there are no usable fonts specified"""
+
+
+class FontManager:
+ """
+ Manages a set of fonts: normal, italic, bold, etc...
+ """
+
+ def __init__(self, font_name, font_size=14):
+ self.font_name = font_name
+ self.font_size = font_size
+ self.fonts = {}
+ self.encoding = None
+ if sys.platform.startswith('win'):
+ if not font_name:
+ self.font_name = DEFAULT_FONT_NAME_WIN
+ self._create_win()
+ elif sys.platform.startswith('darwin'):
+ if not font_name:
+ self.font_name = DEFAULT_FONT_NAME_MAC
+ self._create_mac()
+ else:
+ if not font_name:
+ self.font_name = DEFAULT_FONT_NAME_NIX
+ self._create_nix()
+
+ def _get_nix_font_path(self, name, style):
+ proc = subprocess.Popen(['fc-list', "%s:style=%s" % (name, style), 'file'],
+ stdout=subprocess.PIPE, stderr=None)
+ stdout, _ = proc.communicate()
+ if proc.returncode == 0:
+ lines = stdout.splitlines()
+ for line in lines:
+ if line.startswith(b'Fontconfig warning:'):
+ continue
+ path = line.decode().strip().strip(':')
+ if path:
+ return path
+ return None
+
+ def _create_nix(self):
+ for name in STYLES['NORMAL']:
+ path = self._get_nix_font_path(self.font_name, name)
+ if path is not None:
+ self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size)
+ break
+ else:
+ raise FontNotFound('No usable fonts named: "%s"' %
+ self.font_name)
+ for style in ('ITALIC', 'BOLD', 'BOLDITALIC'):
+ for stylename in STYLES[style]:
+ path = self._get_nix_font_path(self.font_name, stylename)
+ if path is not None:
+ self.fonts[style] = ImageFont.truetype(path, self.font_size)
+ break
+ else:
+ if style == 'BOLDITALIC':
+ self.fonts[style] = self.fonts['BOLD']
+ else:
+ self.fonts[style] = self.fonts['NORMAL']
+
+ def _get_mac_font_path(self, font_map, name, style):
+ return font_map.get((name + ' ' + style).strip().lower())
+
+ def _create_mac(self):
+ font_map = {}
+ for font_dir in (os.path.join(os.getenv("HOME"), 'Library/Fonts/'),
+ '/Library/Fonts/', '/System/Library/Fonts/'):
+ font_map.update(
+ (os.path.splitext(f)[0].lower(), os.path.join(font_dir, f))
+ for f in os.listdir(font_dir)
+ if f.lower().endswith(('ttf', 'ttc')))
+
+ for name in STYLES['NORMAL']:
+ path = self._get_mac_font_path(font_map, self.font_name, name)
+ if path is not None:
+ self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size)
+ break
+ else:
+ raise FontNotFound('No usable fonts named: "%s"' %
+ self.font_name)
+ for style in ('ITALIC', 'BOLD', 'BOLDITALIC'):
+ for stylename in STYLES[style]:
+ path = self._get_mac_font_path(font_map, self.font_name, stylename)
+ if path is not None:
+ self.fonts[style] = ImageFont.truetype(path, self.font_size)
+ break
+ else:
+ if style == 'BOLDITALIC':
+ self.fonts[style] = self.fonts['BOLD']
+ else:
+ self.fonts[style] = self.fonts['NORMAL']
+
+ def _lookup_win(self, key, basename, styles, fail=False):
+ for suffix in ('', ' (TrueType)'):
+ for style in styles:
+ try:
+ valname = '%s%s%s' % (basename, style and ' '+style, suffix)
+ val, _ = _winreg.QueryValueEx(key, valname)
+ return val
+ except OSError:
+ continue
+ else:
+ if fail:
+ raise FontNotFound('Font %s (%s) not found in registry' %
+ (basename, styles[0]))
+ return None
+
+ def _create_win(self):
+ lookuperror = None
+ keynames = [ (_winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows NT\CurrentVersion\Fonts'),
+ (_winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Fonts'),
+ (_winreg.HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows NT\CurrentVersion\Fonts'),
+ (_winreg.HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows\CurrentVersion\Fonts') ]
+ for keyname in keynames:
+ try:
+ key = _winreg.OpenKey(*keyname)
+ try:
+ path = self._lookup_win(key, self.font_name, STYLES['NORMAL'], True)
+ self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size)
+ for style in ('ITALIC', 'BOLD', 'BOLDITALIC'):
+ path = self._lookup_win(key, self.font_name, STYLES[style])
+ if path:
+ self.fonts[style] = ImageFont.truetype(path, self.font_size)
+ else:
+ if style == 'BOLDITALIC':
+ self.fonts[style] = self.fonts['BOLD']
+ else:
+ self.fonts[style] = self.fonts['NORMAL']
+ return
+ except FontNotFound as err:
+ lookuperror = err
+ finally:
+ _winreg.CloseKey(key)
+ except OSError:
+ pass
+ else:
+ # If we get here, we checked all registry keys and had no luck
+ # We can be in one of two situations now:
+ # * All key lookups failed. In this case lookuperror is None and we
+ # will raise a generic error
+ # * At least one lookup failed with a FontNotFound error. In this
+ # case, we will raise that as a more specific error
+ if lookuperror:
+ raise lookuperror
+ raise FontNotFound('Can\'t open Windows font registry key')
+
+ def get_char_size(self):
+ """
+ Get the character size.
+ """
+ return self.get_text_size('M')
+
+ def get_text_size(self, text):
+ """
+ Get the text size (width, height).
+ """
+ font = self.fonts['NORMAL']
+ if hasattr(font, 'getbbox'): # Pillow >= 9.2.0
+ return font.getbbox(text)[2:4]
+ else:
+ return font.getsize(text)
+
+ def get_font(self, bold, oblique):
+ """
+ Get the font based on bold and italic flags.
+ """
+ if bold and oblique:
+ return self.fonts['BOLDITALIC']
+ elif bold:
+ return self.fonts['BOLD']
+ elif oblique:
+ return self.fonts['ITALIC']
+ else:
+ return self.fonts['NORMAL']
+
+
+class ImageFormatter(Formatter):
+ """
+ Create a PNG image from source code. This uses the Python Imaging Library to
+ generate a pixmap from the source code.
+
+ .. versionadded:: 0.10
+
+ Additional options accepted:
+
+ `image_format`
+ An image format to output to that is recognised by PIL, these include:
+
+ * "PNG" (default)
+ * "JPEG"
+ * "BMP"
+ * "GIF"
+
+ `line_pad`
+ The extra spacing (in pixels) between each line of text.
+
+ Default: 2
+
+ `font_name`
+ The font name to be used as the base font from which others, such as
+ bold and italic fonts will be generated. This really should be a
+ monospace font to look sane.
+
+ Default: "Courier New" on Windows, "Menlo" on Mac OS, and
+ "DejaVu Sans Mono" on \\*nix
+
+ `font_size`
+ The font size in points to be used.
+
+ Default: 14
+
+ `image_pad`
+ The padding, in pixels to be used at each edge of the resulting image.
+
+ Default: 10
+
+ `line_numbers`
+ Whether line numbers should be shown: True/False
+
+ Default: True
+
+ `line_number_start`
+ The line number of the first line.
+
+ Default: 1
+
+ `line_number_step`
+ The step used when printing line numbers.
+
+ Default: 1
+
+ `line_number_bg`
+ The background colour (in "#123456" format) of the line number bar, or
+ None to use the style background color.
+
+ Default: "#eed"
+
+ `line_number_fg`
+ The text color of the line numbers (in "#123456"-like format).
+
+ Default: "#886"
+
+ `line_number_chars`
+ The number of columns of line numbers allowable in the line number
+ margin.
+
+ Default: 2
+
+ `line_number_bold`
+ Whether line numbers will be bold: True/False
+
+ Default: False
+
+ `line_number_italic`
+ Whether line numbers will be italicized: True/False
+
+ Default: False
+
+ `line_number_separator`
+ Whether a line will be drawn between the line number area and the
+ source code area: True/False
+
+ Default: True
+
+ `line_number_pad`
+ The horizontal padding (in pixels) between the line number margin, and
+ the source code area.
+
+ Default: 6
+
+ `hl_lines`
+ Specify a list of lines to be highlighted.
+
+ .. versionadded:: 1.2
+
+ Default: empty list
+
+ `hl_color`
+ Specify the color for highlighting lines.
+
+ .. versionadded:: 1.2
+
+ Default: highlight color of the selected style
+ """
+
+ # Required by the pygments mapper
+ name = 'img'
+ aliases = ['img', 'IMG', 'png']
+ filenames = ['*.png']
+
+ unicodeoutput = False
+
+ default_image_format = 'png'
+
+ def __init__(self, **options):
+ """
+ See the class docstring for explanation of options.
+ """
+ if not pil_available:
+ raise PilNotAvailable(
+ 'Python Imaging Library is required for this formatter')
+ Formatter.__init__(self, **options)
+ self.encoding = 'latin1' # let pygments.format() do the right thing
+ # Read the style
+ self.styles = dict(self.style)
+ if self.style.background_color is None:
+ self.background_color = '#fff'
+ else:
+ self.background_color = self.style.background_color
+ # Image options
+ self.image_format = get_choice_opt(
+ options, 'image_format', ['png', 'jpeg', 'gif', 'bmp'],
+ self.default_image_format, normcase=True)
+ self.image_pad = get_int_opt(options, 'image_pad', 10)
+ self.line_pad = get_int_opt(options, 'line_pad', 2)
+ # The fonts
+ fontsize = get_int_opt(options, 'font_size', 14)
+ self.fonts = FontManager(options.get('font_name', ''), fontsize)
+ self.fontw, self.fonth = self.fonts.get_char_size()
+ # Line number options
+ self.line_number_fg = options.get('line_number_fg', '#886')
+ self.line_number_bg = options.get('line_number_bg', '#eed')
+ self.line_number_chars = get_int_opt(options,
+ 'line_number_chars', 2)
+ self.line_number_bold = get_bool_opt(options,
+ 'line_number_bold', False)
+ self.line_number_italic = get_bool_opt(options,
+ 'line_number_italic', False)
+ self.line_number_pad = get_int_opt(options, 'line_number_pad', 6)
+ self.line_numbers = get_bool_opt(options, 'line_numbers', True)
+ self.line_number_separator = get_bool_opt(options,
+ 'line_number_separator', True)
+ self.line_number_step = get_int_opt(options, 'line_number_step', 1)
+ self.line_number_start = get_int_opt(options, 'line_number_start', 1)
+ if self.line_numbers:
+ self.line_number_width = (self.fontw * self.line_number_chars +
+ self.line_number_pad * 2)
+ else:
+ self.line_number_width = 0
+ self.hl_lines = []
+ hl_lines_str = get_list_opt(options, 'hl_lines', [])
+ for line in hl_lines_str:
+ try:
+ self.hl_lines.append(int(line))
+ except ValueError:
+ pass
+ self.hl_color = options.get('hl_color',
+ self.style.highlight_color) or '#f90'
+ self.drawables = []
+
+ def get_style_defs(self, arg=''):
+ raise NotImplementedError('The -S option is meaningless for the image '
+ 'formatter. Use -O style=<stylename> instead.')
+
+ def _get_line_height(self):
+ """
+ Get the height of a line.
+ """
+ return self.fonth + self.line_pad
+
+ def _get_line_y(self, lineno):
+ """
+ Get the Y coordinate of a line number.
+ """
+ return lineno * self._get_line_height() + self.image_pad
+
+ def _get_char_width(self):
+ """
+ Get the width of a character.
+ """
+ return self.fontw
+
+ def _get_char_x(self, linelength):
+ """
+ Get the X coordinate of a character position.
+ """
+ return linelength + self.image_pad + self.line_number_width
+
+ def _get_text_pos(self, linelength, lineno):
+ """
+ Get the actual position for a character and line position.
+ """
+ return self._get_char_x(linelength), self._get_line_y(lineno)
+
+ def _get_linenumber_pos(self, lineno):
+ """
+ Get the actual position for the start of a line number.
+ """
+ return (self.image_pad, self._get_line_y(lineno))
+
+ def _get_text_color(self, style):
+ """
+ Get the correct color for the token from the style.
+ """
+ if style['color'] is not None:
+ fill = '#' + style['color']
+ else:
+ fill = '#000'
+ return fill
+
+ def _get_text_bg_color(self, style):
+ """
+ Get the correct background color for the token from the style.
+ """
+ if style['bgcolor'] is not None:
+ bg_color = '#' + style['bgcolor']
+ else:
+ bg_color = None
+ return bg_color
+
+ def _get_style_font(self, style):
+ """
+ Get the correct font for the style.
+ """
+ return self.fonts.get_font(style['bold'], style['italic'])
+
+ def _get_image_size(self, maxlinelength, maxlineno):
+ """
+ Get the required image size.
+ """
+ return (self._get_char_x(maxlinelength) + self.image_pad,
+ self._get_line_y(maxlineno + 0) + self.image_pad)
+
+ def _draw_linenumber(self, posno, lineno):
+ """
+ Remember a line number drawable to paint later.
+ """
+ self._draw_text(
+ self._get_linenumber_pos(posno),
+ str(lineno).rjust(self.line_number_chars),
+ font=self.fonts.get_font(self.line_number_bold,
+ self.line_number_italic),
+ text_fg=self.line_number_fg,
+ text_bg=None,
+ )
+
+ def _draw_text(self, pos, text, font, text_fg, text_bg):
+ """
+ Remember a single drawable tuple to paint later.
+ """
+ self.drawables.append((pos, text, font, text_fg, text_bg))
+
+ def _create_drawables(self, tokensource):
+ """
+ Create drawables for the token content.
+ """
+ lineno = charno = maxcharno = 0
+ maxlinelength = linelength = 0
+ for ttype, value in tokensource:
+ while ttype not in self.styles:
+ ttype = ttype.parent
+ style = self.styles[ttype]
+ # TODO: make sure tab expansion happens earlier in the chain. It
+ # really ought to be done on the input, as to do it right here is
+ # quite complex.
+ value = value.expandtabs(4)
+ lines = value.splitlines(True)
+ # print lines
+ for i, line in enumerate(lines):
+ temp = line.rstrip('\n')
+ if temp:
+ self._draw_text(
+ self._get_text_pos(linelength, lineno),
+ temp,
+ font = self._get_style_font(style),
+ text_fg = self._get_text_color(style),
+ text_bg = self._get_text_bg_color(style),
+ )
+ temp_width, _ = self.fonts.get_text_size(temp)
+ linelength += temp_width
+ maxlinelength = max(maxlinelength, linelength)
+ charno += len(temp)
+ maxcharno = max(maxcharno, charno)
+ if line.endswith('\n'):
+ # add a line for each extra line in the value
+ linelength = 0
+ charno = 0
+ lineno += 1
+ self.maxlinelength = maxlinelength
+ self.maxcharno = maxcharno
+ self.maxlineno = lineno
+
+ def _draw_line_numbers(self):
+ """
+ Create drawables for the line numbers.
+ """
+ if not self.line_numbers:
+ return
+ for p in range(self.maxlineno):
+ n = p + self.line_number_start
+ if (n % self.line_number_step) == 0:
+ self._draw_linenumber(p, n)
+
+ def _paint_line_number_bg(self, im):
+ """
+ Paint the line number background on the image.
+ """
+ if not self.line_numbers:
+ return
+ if self.line_number_fg is None:
+ return
+ draw = ImageDraw.Draw(im)
+ recth = im.size[-1]
+ rectw = self.image_pad + self.line_number_width - self.line_number_pad
+ draw.rectangle([(0, 0), (rectw, recth)],
+ fill=self.line_number_bg)
+ if self.line_number_separator:
+ draw.line([(rectw, 0), (rectw, recth)], fill=self.line_number_fg)
+ del draw
+
+ def format(self, tokensource, outfile):
+ """
+ Format ``tokensource``, an iterable of ``(tokentype, tokenstring)``
+ tuples and write it into ``outfile``.
+
+ This implementation calculates where it should draw each token on the
+ pixmap, then calculates the required pixmap size and draws the items.
+ """
+ self._create_drawables(tokensource)
+ self._draw_line_numbers()
+ im = Image.new(
+ 'RGB',
+ self._get_image_size(self.maxlinelength, self.maxlineno),
+ self.background_color
+ )
+ self._paint_line_number_bg(im)
+ draw = ImageDraw.Draw(im)
+ # Highlight
+ if self.hl_lines:
+ x = self.image_pad + self.line_number_width - self.line_number_pad + 1
+ recth = self._get_line_height()
+ rectw = im.size[0] - x
+ for linenumber in self.hl_lines:
+ y = self._get_line_y(linenumber - 1)
+ draw.rectangle([(x, y), (x + rectw, y + recth)],
+ fill=self.hl_color)
+ for pos, value, font, text_fg, text_bg in self.drawables:
+ if text_bg:
+ text_size = draw.textsize(text=value, font=font)
+ draw.rectangle([pos[0], pos[1], pos[0] + text_size[0], pos[1] + text_size[1]], fill=text_bg)
+ draw.text(pos, value, font=font, fill=text_fg)
+ im.save(outfile, self.image_format.upper())
+
+
+# Add one formatter per format, so that the "-f gif" option gives the correct result
+# when used in pygmentize.
+
+class GifImageFormatter(ImageFormatter):
+ """
+ Create a GIF image from source code. This uses the Python Imaging Library to
+ generate a pixmap from the source code.
+
+ .. versionadded:: 1.0
+ """
+
+ name = 'img_gif'
+ aliases = ['gif']
+ filenames = ['*.gif']
+ default_image_format = 'gif'
+
+
+class JpgImageFormatter(ImageFormatter):
+ """
+ Create a JPEG image from source code. This uses the Python Imaging Library to
+ generate a pixmap from the source code.
+
+ .. versionadded:: 1.0
+ """
+
+ name = 'img_jpg'
+ aliases = ['jpg', 'jpeg']
+ filenames = ['*.jpg']
+ default_image_format = 'jpeg'
+
+
+class BmpImageFormatter(ImageFormatter):
+ """
+ Create a bitmap image from source code. This uses the Python Imaging Library to
+ generate a pixmap from the source code.
+
+ .. versionadded:: 1.0
+ """
+
+ name = 'img_bmp'
+ aliases = ['bmp', 'bitmap']
+ filenames = ['*.bmp']
+ default_image_format = 'bmp'
diff --git a/src/pip/_vendor/pygments/formatters/irc.py b/src/pip/_vendor/pygments/formatters/irc.py
new file mode 100644
index 000000000..3f6d52deb
--- /dev/null
+++ b/src/pip/_vendor/pygments/formatters/irc.py
@@ -0,0 +1,179 @@
+"""
+ pygments.formatters.irc
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+ Formatter for IRC output
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.token import Keyword, Name, Comment, String, Error, \
+ Number, Operator, Generic, Token, Whitespace
+from pip._vendor.pygments.util import get_choice_opt
+
+
+__all__ = ['IRCFormatter']
+
+
+#: Map token types to a tuple of color values for light and dark
+#: backgrounds.
+IRC_COLORS = {
+ Token: ('', ''),
+
+ Whitespace: ('gray', 'brightblack'),
+ Comment: ('gray', 'brightblack'),
+ Comment.Preproc: ('cyan', 'brightcyan'),
+ Keyword: ('blue', 'brightblue'),
+ Keyword.Type: ('cyan', 'brightcyan'),
+ Operator.Word: ('magenta', 'brightcyan'),
+ Name.Builtin: ('cyan', 'brightcyan'),
+ Name.Function: ('green', 'brightgreen'),
+ Name.Namespace: ('_cyan_', '_brightcyan_'),
+ Name.Class: ('_green_', '_brightgreen_'),
+ Name.Exception: ('cyan', 'brightcyan'),
+ Name.Decorator: ('brightblack', 'gray'),
+ Name.Variable: ('red', 'brightred'),
+ Name.Constant: ('red', 'brightred'),
+ Name.Attribute: ('cyan', 'brightcyan'),
+ Name.Tag: ('brightblue', 'brightblue'),
+ String: ('yellow', 'yellow'),
+ Number: ('blue', 'brightblue'),
+
+ Generic.Deleted: ('brightred', 'brightred'),
+ Generic.Inserted: ('green', 'brightgreen'),
+ Generic.Heading: ('**', '**'),
+ Generic.Subheading: ('*magenta*', '*brightmagenta*'),
+ Generic.Error: ('brightred', 'brightred'),
+
+ Error: ('_brightred_', '_brightred_'),
+}
+
+
+IRC_COLOR_MAP = {
+ 'white': 0,
+ 'black': 1,
+ 'blue': 2,
+ 'brightgreen': 3,
+ 'brightred': 4,
+ 'yellow': 5,
+ 'magenta': 6,
+ 'orange': 7,
+ 'green': 7, #compat w/ ansi
+ 'brightyellow': 8,
+ 'lightgreen': 9,
+ 'brightcyan': 9, # compat w/ ansi
+ 'cyan': 10,
+ 'lightblue': 11,
+ 'red': 11, # compat w/ ansi
+ 'brightblue': 12,
+ 'brightmagenta': 13,
+ 'brightblack': 14,
+ 'gray': 15,
+}
+
+def ircformat(color, text):
+ if len(color) < 1:
+ return text
+ add = sub = ''
+ if '_' in color: # italic
+ add += '\x1D'
+ sub = '\x1D' + sub
+ color = color.strip('_')
+ if '*' in color: # bold
+ add += '\x02'
+ sub = '\x02' + sub
+ color = color.strip('*')
+ # underline (\x1F) not supported
+ # backgrounds (\x03FF,BB) not supported
+ if len(color) > 0: # actual color - may have issues with ircformat("red", "blah")+"10" type stuff
+ add += '\x03' + str(IRC_COLOR_MAP[color]).zfill(2)
+ sub = '\x03' + sub
+ return add + text + sub
+ return '<'+add+'>'+text+'</'+sub+'>'
+
+
+class IRCFormatter(Formatter):
+ r"""
+ Format tokens with IRC color sequences
+
+ The `get_style_defs()` method doesn't do anything special since there is
+ no support for common styles.
+
+ Options accepted:
+
+ `bg`
+ Set to ``"light"`` or ``"dark"`` depending on the terminal's background
+ (default: ``"light"``).
+
+ `colorscheme`
+ A dictionary mapping token types to (lightbg, darkbg) color names or
+ ``None`` (default: ``None`` = use builtin colorscheme).
+
+ `linenos`
+ Set to ``True`` to have line numbers in the output as well
+ (default: ``False`` = no line numbers).
+ """
+ name = 'IRC'
+ aliases = ['irc', 'IRC']
+ filenames = []
+
+ def __init__(self, **options):
+ Formatter.__init__(self, **options)
+ self.darkbg = get_choice_opt(options, 'bg',
+ ['light', 'dark'], 'light') == 'dark'
+ self.colorscheme = options.get('colorscheme', None) or IRC_COLORS
+ self.linenos = options.get('linenos', False)
+ self._lineno = 0
+
+ def _write_lineno(self, outfile):
+ self._lineno += 1
+ outfile.write("\n%04d: " % self._lineno)
+
+ def _format_unencoded_with_lineno(self, tokensource, outfile):
+ self._write_lineno(outfile)
+
+ for ttype, value in tokensource:
+ if value.endswith("\n"):
+ self._write_lineno(outfile)
+ value = value[:-1]
+ color = self.colorscheme.get(ttype)
+ while color is None:
+ ttype = ttype.parent
+ color = self.colorscheme.get(ttype)
+ if color:
+ color = color[self.darkbg]
+ spl = value.split('\n')
+ for line in spl[:-1]:
+ self._write_lineno(outfile)
+ if line:
+ outfile.write(ircformat(color, line[:-1]))
+ if spl[-1]:
+ outfile.write(ircformat(color, spl[-1]))
+ else:
+ outfile.write(value)
+
+ outfile.write("\n")
+
+ def format_unencoded(self, tokensource, outfile):
+ if self.linenos:
+ self._format_unencoded_with_lineno(tokensource, outfile)
+ return
+
+ for ttype, value in tokensource:
+ color = self.colorscheme.get(ttype)
+ while color is None:
+ ttype = ttype[:-1]
+ color = self.colorscheme.get(ttype)
+ if color:
+ color = color[self.darkbg]
+ spl = value.split('\n')
+ for line in spl[:-1]:
+ if line:
+ outfile.write(ircformat(color, line))
+ outfile.write('\n')
+ if spl[-1]:
+ outfile.write(ircformat(color, spl[-1]))
+ else:
+ outfile.write(value)
diff --git a/src/pip/_vendor/pygments/formatters/latex.py b/src/pip/_vendor/pygments/formatters/latex.py
new file mode 100644
index 000000000..4a7375a5c
--- /dev/null
+++ b/src/pip/_vendor/pygments/formatters/latex.py
@@ -0,0 +1,521 @@
+"""
+ pygments.formatters.latex
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Formatter for LaTeX fancyvrb output.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from io import StringIO
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.lexer import Lexer, do_insertions
+from pip._vendor.pygments.token import Token, STANDARD_TYPES
+from pip._vendor.pygments.util import get_bool_opt, get_int_opt
+
+
+__all__ = ['LatexFormatter']
+
+
+def escape_tex(text, commandprefix):
+ return text.replace('\\', '\x00'). \
+ replace('{', '\x01'). \
+ replace('}', '\x02'). \
+ replace('\x00', r'\%sZbs{}' % commandprefix). \
+ replace('\x01', r'\%sZob{}' % commandprefix). \
+ replace('\x02', r'\%sZcb{}' % commandprefix). \
+ replace('^', r'\%sZca{}' % commandprefix). \
+ replace('_', r'\%sZus{}' % commandprefix). \
+ replace('&', r'\%sZam{}' % commandprefix). \
+ replace('<', r'\%sZlt{}' % commandprefix). \
+ replace('>', r'\%sZgt{}' % commandprefix). \
+ replace('#', r'\%sZsh{}' % commandprefix). \
+ replace('%', r'\%sZpc{}' % commandprefix). \
+ replace('$', r'\%sZdl{}' % commandprefix). \
+ replace('-', r'\%sZhy{}' % commandprefix). \
+ replace("'", r'\%sZsq{}' % commandprefix). \
+ replace('"', r'\%sZdq{}' % commandprefix). \
+ replace('~', r'\%sZti{}' % commandprefix)
+
+
+DOC_TEMPLATE = r'''
+\documentclass{%(docclass)s}
+\usepackage{fancyvrb}
+\usepackage{color}
+\usepackage[%(encoding)s]{inputenc}
+%(preamble)s
+
+%(styledefs)s
+
+\begin{document}
+
+\section*{%(title)s}
+
+%(code)s
+\end{document}
+'''
+
+## Small explanation of the mess below :)
+#
+# The previous version of the LaTeX formatter just assigned a command to
+# each token type defined in the current style. That obviously is
+# problematic if the highlighted code is produced for a different style
+# than the style commands themselves.
+#
+# This version works much like the HTML formatter which assigns multiple
+# CSS classes to each <span> tag, from the most specific to the least
+# specific token type, thus falling back to the parent token type if one
+# is not defined. Here, the classes are there too and use the same short
+# forms given in token.STANDARD_TYPES.
+#
+# Highlighted code now only uses one custom command, which by default is
+# \PY and selectable by the commandprefix option (and in addition the
+# escapes \PYZat, \PYZlb and \PYZrb which haven't been renamed for
+# backwards compatibility purposes).
+#
+# \PY has two arguments: the classes, separated by +, and the text to
+# render in that style. The classes are resolved into the respective
+# style commands by magic, which serves to ignore unknown classes.
+#
+# The magic macros are:
+# * \PY@it, \PY@bf, etc. are unconditionally wrapped around the text
+# to render in \PY@do. Their definition determines the style.
+# * \PY@reset resets \PY@it etc. to do nothing.
+# * \PY@toks parses the list of classes, using magic inspired by the
+# keyval package (but modified to use plusses instead of commas
+# because fancyvrb redefines commas inside its environments).
+# * \PY@tok processes one class, calling the \PY@tok@classname command
+# if it exists.
+# * \PY@tok@classname sets the \PY@it etc. to reflect the chosen style
+# for its class.
+# * \PY resets the style, parses the classnames and then calls \PY@do.
+#
+# Tip: to read this code, print it out in substituted form using e.g.
+# >>> print STYLE_TEMPLATE % {'cp': 'PY'}
+
+STYLE_TEMPLATE = r'''
+\makeatletter
+\def\%(cp)s@reset{\let\%(cp)s@it=\relax \let\%(cp)s@bf=\relax%%
+ \let\%(cp)s@ul=\relax \let\%(cp)s@tc=\relax%%
+ \let\%(cp)s@bc=\relax \let\%(cp)s@ff=\relax}
+\def\%(cp)s@tok#1{\csname %(cp)s@tok@#1\endcsname}
+\def\%(cp)s@toks#1+{\ifx\relax#1\empty\else%%
+ \%(cp)s@tok{#1}\expandafter\%(cp)s@toks\fi}
+\def\%(cp)s@do#1{\%(cp)s@bc{\%(cp)s@tc{\%(cp)s@ul{%%
+ \%(cp)s@it{\%(cp)s@bf{\%(cp)s@ff{#1}}}}}}}
+\def\%(cp)s#1#2{\%(cp)s@reset\%(cp)s@toks#1+\relax+\%(cp)s@do{#2}}
+
+%(styles)s
+
+\def\%(cp)sZbs{\char`\\}
+\def\%(cp)sZus{\char`\_}
+\def\%(cp)sZob{\char`\{}
+\def\%(cp)sZcb{\char`\}}
+\def\%(cp)sZca{\char`\^}
+\def\%(cp)sZam{\char`\&}
+\def\%(cp)sZlt{\char`\<}
+\def\%(cp)sZgt{\char`\>}
+\def\%(cp)sZsh{\char`\#}
+\def\%(cp)sZpc{\char`\%%}
+\def\%(cp)sZdl{\char`\$}
+\def\%(cp)sZhy{\char`\-}
+\def\%(cp)sZsq{\char`\'}
+\def\%(cp)sZdq{\char`\"}
+\def\%(cp)sZti{\char`\~}
+%% for compatibility with earlier versions
+\def\%(cp)sZat{@}
+\def\%(cp)sZlb{[}
+\def\%(cp)sZrb{]}
+\makeatother
+'''
+
+
+def _get_ttype_name(ttype):
+ fname = STANDARD_TYPES.get(ttype)
+ if fname:
+ return fname
+ aname = ''
+ while fname is None:
+ aname = ttype[-1] + aname
+ ttype = ttype.parent
+ fname = STANDARD_TYPES.get(ttype)
+ return fname + aname
+
+
+class LatexFormatter(Formatter):
+ r"""
+ Format tokens as LaTeX code. This needs the `fancyvrb` and `color`
+ standard packages.
+
+ Without the `full` option, code is formatted as one ``Verbatim``
+ environment, like this:
+
+ .. sourcecode:: latex
+
+ \begin{Verbatim}[commandchars=\\\{\}]
+ \PY{k}{def }\PY{n+nf}{foo}(\PY{n}{bar}):
+ \PY{k}{pass}
+ \end{Verbatim}
+
+ Wrapping can be disabled using the `nowrap` option.
+
+ The special command used here (``\PY``) and all the other macros it needs
+ are output by the `get_style_defs` method.
+
+ With the `full` option, a complete LaTeX document is output, including
+ the command definitions in the preamble.
+
+ The `get_style_defs()` method of a `LatexFormatter` returns a string
+ containing ``\def`` commands defining the macros needed inside the
+ ``Verbatim`` environments.
+
+ Additional options accepted:
+
+ `nowrap`
+ If set to ``True``, don't wrap the tokens at all, not even inside a
+ ``\begin{Verbatim}`` environment. This disables most other options
+ (default: ``False``).
+
+ `style`
+ The style to use, can be a string or a Style subclass (default:
+ ``'default'``).
+
+ `full`
+ Tells the formatter to output a "full" document, i.e. a complete
+ self-contained document (default: ``False``).
+
+ `title`
+ If `full` is true, the title that should be used to caption the
+ document (default: ``''``).
+
+ `docclass`
+ If the `full` option is enabled, this is the document class to use
+ (default: ``'article'``).
+
+ `preamble`
+ If the `full` option is enabled, this can be further preamble commands,
+ e.g. ``\usepackage`` (default: ``''``).
+
+ `linenos`
+ If set to ``True``, output line numbers (default: ``False``).
+
+ `linenostart`
+ The line number for the first line (default: ``1``).
+
+ `linenostep`
+ If set to a number n > 1, only every nth line number is printed.
+
+ `verboptions`
+ Additional options given to the Verbatim environment (see the *fancyvrb*
+ docs for possible values) (default: ``''``).
+
+ `commandprefix`
+ The LaTeX commands used to produce colored output are constructed
+ using this prefix and some letters (default: ``'PY'``).
+
+ .. versionadded:: 0.7
+ .. versionchanged:: 0.10
+ The default is now ``'PY'`` instead of ``'C'``.
+
+ `texcomments`
+ If set to ``True``, enables LaTeX comment lines. That is, LaTex markup
+ in comment tokens is not escaped so that LaTeX can render it (default:
+ ``False``).
+
+ .. versionadded:: 1.2
+
+ `mathescape`
+ If set to ``True``, enables LaTeX math mode escape in comments. That
+ is, ``'$...$'`` inside a comment will trigger math mode (default:
+ ``False``).
+
+ .. versionadded:: 1.2
+
+ `escapeinside`
+ If set to a string of length 2, enables escaping to LaTeX. Text
+ delimited by these 2 characters is read as LaTeX code and
+ typeset accordingly. It has no effect in string literals. It has
+ no effect in comments if `texcomments` or `mathescape` is
+ set. (default: ``''``).
+
+ .. versionadded:: 2.0
+
+ `envname`
+ Allows you to pick an alternative environment name replacing Verbatim.
+ The alternate environment still has to support Verbatim's option syntax.
+ (default: ``'Verbatim'``).
+
+ .. versionadded:: 2.0
+ """
+ name = 'LaTeX'
+ aliases = ['latex', 'tex']
+ filenames = ['*.tex']
+
+ def __init__(self, **options):
+ Formatter.__init__(self, **options)
+ self.nowrap = get_bool_opt(options, 'nowrap', False)
+ self.docclass = options.get('docclass', 'article')
+ self.preamble = options.get('preamble', '')
+ self.linenos = get_bool_opt(options, 'linenos', False)
+ self.linenostart = abs(get_int_opt(options, 'linenostart', 1))
+ self.linenostep = abs(get_int_opt(options, 'linenostep', 1))
+ self.verboptions = options.get('verboptions', '')
+ self.nobackground = get_bool_opt(options, 'nobackground', False)
+ self.commandprefix = options.get('commandprefix', 'PY')
+ self.texcomments = get_bool_opt(options, 'texcomments', False)
+ self.mathescape = get_bool_opt(options, 'mathescape', False)
+ self.escapeinside = options.get('escapeinside', '')
+ if len(self.escapeinside) == 2:
+ self.left = self.escapeinside[0]
+ self.right = self.escapeinside[1]
+ else:
+ self.escapeinside = ''
+ self.envname = options.get('envname', 'Verbatim')
+
+ self._create_stylesheet()
+
+ def _create_stylesheet(self):
+ t2n = self.ttype2name = {Token: ''}
+ c2d = self.cmd2def = {}
+ cp = self.commandprefix
+
+ def rgbcolor(col):
+ if col:
+ return ','.join(['%.2f' % (int(col[i] + col[i + 1], 16) / 255.0)
+ for i in (0, 2, 4)])
+ else:
+ return '1,1,1'
+
+ for ttype, ndef in self.style:
+ name = _get_ttype_name(ttype)
+ cmndef = ''
+ if ndef['bold']:
+ cmndef += r'\let\$$@bf=\textbf'
+ if ndef['italic']:
+ cmndef += r'\let\$$@it=\textit'
+ if ndef['underline']:
+ cmndef += r'\let\$$@ul=\underline'
+ if ndef['roman']:
+ cmndef += r'\let\$$@ff=\textrm'
+ if ndef['sans']:
+ cmndef += r'\let\$$@ff=\textsf'
+ if ndef['mono']:
+ cmndef += r'\let\$$@ff=\textsf'
+ if ndef['color']:
+ cmndef += (r'\def\$$@tc##1{\textcolor[rgb]{%s}{##1}}' %
+ rgbcolor(ndef['color']))
+ if ndef['border']:
+ cmndef += (r'\def\$$@bc##1{{\setlength{\fboxsep}{\string -\fboxrule}'
+ r'\fcolorbox[rgb]{%s}{%s}{\strut ##1}}}' %
+ (rgbcolor(ndef['border']),
+ rgbcolor(ndef['bgcolor'])))
+ elif ndef['bgcolor']:
+ cmndef += (r'\def\$$@bc##1{{\setlength{\fboxsep}{0pt}'
+ r'\colorbox[rgb]{%s}{\strut ##1}}}' %
+ rgbcolor(ndef['bgcolor']))
+ if cmndef == '':
+ continue
+ cmndef = cmndef.replace('$$', cp)
+ t2n[ttype] = name
+ c2d[name] = cmndef
+
+ def get_style_defs(self, arg=''):
+ """
+ Return the command sequences needed to define the commands
+ used to format text in the verbatim environment. ``arg`` is ignored.
+ """
+ cp = self.commandprefix
+ styles = []
+ for name, definition in self.cmd2def.items():
+ styles.append(r'\@namedef{%s@tok@%s}{%s}' % (cp, name, definition))
+ return STYLE_TEMPLATE % {'cp': self.commandprefix,
+ 'styles': '\n'.join(styles)}
+
+ def format_unencoded(self, tokensource, outfile):
+ # TODO: add support for background colors
+ t2n = self.ttype2name
+ cp = self.commandprefix
+
+ if self.full:
+ realoutfile = outfile
+ outfile = StringIO()
+
+ if not self.nowrap:
+ outfile.write('\\begin{' + self.envname + '}[commandchars=\\\\\\{\\}')
+ if self.linenos:
+ start, step = self.linenostart, self.linenostep
+ outfile.write(',numbers=left' +
+ (start and ',firstnumber=%d' % start or '') +
+ (step and ',stepnumber=%d' % step or ''))
+ if self.mathescape or self.texcomments or self.escapeinside:
+ outfile.write(',codes={\\catcode`\\$=3\\catcode`\\^=7'
+ '\\catcode`\\_=8\\relax}')
+ if self.verboptions:
+ outfile.write(',' + self.verboptions)
+ outfile.write(']\n')
+
+ for ttype, value in tokensource:
+ if ttype in Token.Comment:
+ if self.texcomments:
+ # Try to guess comment starting lexeme and escape it ...
+ start = value[0:1]
+ for i in range(1, len(value)):
+ if start[0] != value[i]:
+ break
+ start += value[i]
+
+ value = value[len(start):]
+ start = escape_tex(start, cp)
+
+ # ... but do not escape inside comment.
+ value = start + value
+ elif self.mathescape:
+ # Only escape parts not inside a math environment.
+ parts = value.split('$')
+ in_math = False
+ for i, part in enumerate(parts):
+ if not in_math:
+ parts[i] = escape_tex(part, cp)
+ in_math = not in_math
+ value = '$'.join(parts)
+ elif self.escapeinside:
+ text = value
+ value = ''
+ while text:
+ a, sep1, text = text.partition(self.left)
+ if sep1:
+ b, sep2, text = text.partition(self.right)
+ if sep2:
+ value += escape_tex(a, cp) + b
+ else:
+ value += escape_tex(a + sep1 + b, cp)
+ else:
+ value += escape_tex(a, cp)
+ else:
+ value = escape_tex(value, cp)
+ elif ttype not in Token.Escape:
+ value = escape_tex(value, cp)
+ styles = []
+ while ttype is not Token:
+ try:
+ styles.append(t2n[ttype])
+ except KeyError:
+ # not in current style
+ styles.append(_get_ttype_name(ttype))
+ ttype = ttype.parent
+ styleval = '+'.join(reversed(styles))
+ if styleval:
+ spl = value.split('\n')
+ for line in spl[:-1]:
+ if line:
+ outfile.write("\\%s{%s}{%s}" % (cp, styleval, line))
+ outfile.write('\n')
+ if spl[-1]:
+ outfile.write("\\%s{%s}{%s}" % (cp, styleval, spl[-1]))
+ else:
+ outfile.write(value)
+
+ if not self.nowrap:
+ outfile.write('\\end{' + self.envname + '}\n')
+
+ if self.full:
+ encoding = self.encoding or 'utf8'
+ # map known existings encodings from LaTeX distribution
+ encoding = {
+ 'utf_8': 'utf8',
+ 'latin_1': 'latin1',
+ 'iso_8859_1': 'latin1',
+ }.get(encoding.replace('-', '_'), encoding)
+ realoutfile.write(DOC_TEMPLATE %
+ dict(docclass = self.docclass,
+ preamble = self.preamble,
+ title = self.title,
+ encoding = encoding,
+ styledefs = self.get_style_defs(),
+ code = outfile.getvalue()))
+
+
+class LatexEmbeddedLexer(Lexer):
+ """
+ This lexer takes one lexer as argument, the lexer for the language
+ being formatted, and the left and right delimiters for escaped text.
+
+ First everything is scanned using the language lexer to obtain
+ strings and comments. All other consecutive tokens are merged and
+ the resulting text is scanned for escaped segments, which are given
+ the Token.Escape type. Finally text that is not escaped is scanned
+ again with the language lexer.
+ """
+ def __init__(self, left, right, lang, **options):
+ self.left = left
+ self.right = right
+ self.lang = lang
+ Lexer.__init__(self, **options)
+
+ def get_tokens_unprocessed(self, text):
+ # find and remove all the escape tokens (replace with an empty string)
+ # this is very similar to DelegatingLexer.get_tokens_unprocessed.
+ buffered = ''
+ insertions = []
+ insertion_buf = []
+ for i, t, v in self._find_safe_escape_tokens(text):
+ if t is None:
+ if insertion_buf:
+ insertions.append((len(buffered), insertion_buf))
+ insertion_buf = []
+ buffered += v
+ else:
+ insertion_buf.append((i, t, v))
+ if insertion_buf:
+ insertions.append((len(buffered), insertion_buf))
+ return do_insertions(insertions,
+ self.lang.get_tokens_unprocessed(buffered))
+
+ def _find_safe_escape_tokens(self, text):
+ """ find escape tokens that are not in strings or comments """
+ for i, t, v in self._filter_to(
+ self.lang.get_tokens_unprocessed(text),
+ lambda t: t in Token.Comment or t in Token.String
+ ):
+ if t is None:
+ for i2, t2, v2 in self._find_escape_tokens(v):
+ yield i + i2, t2, v2
+ else:
+ yield i, None, v
+
+ def _filter_to(self, it, pred):
+ """ Keep only the tokens that match `pred`, merge the others together """
+ buf = ''
+ idx = 0
+ for i, t, v in it:
+ if pred(t):
+ if buf:
+ yield idx, None, buf
+ buf = ''
+ yield i, t, v
+ else:
+ if not buf:
+ idx = i
+ buf += v
+ if buf:
+ yield idx, None, buf
+
+ def _find_escape_tokens(self, text):
+ """ Find escape tokens within text, give token=None otherwise """
+ index = 0
+ while text:
+ a, sep1, text = text.partition(self.left)
+ if a:
+ yield index, None, a
+ index += len(a)
+ if sep1:
+ b, sep2, text = text.partition(self.right)
+ if sep2:
+ yield index + len(sep1), Token.Escape, b
+ index += len(sep1) + len(b) + len(sep2)
+ else:
+ yield index, Token.Error, sep1
+ index += len(sep1)
+ text = b
diff --git a/src/pip/_vendor/pygments/formatters/other.py b/src/pip/_vendor/pygments/formatters/other.py
new file mode 100644
index 000000000..1e39cd42a
--- /dev/null
+++ b/src/pip/_vendor/pygments/formatters/other.py
@@ -0,0 +1,161 @@
+"""
+ pygments.formatters.other
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Other formatters: NullFormatter, RawTokenFormatter.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.util import get_choice_opt
+from pip._vendor.pygments.token import Token
+from pip._vendor.pygments.console import colorize
+
+__all__ = ['NullFormatter', 'RawTokenFormatter', 'TestcaseFormatter']
+
+
+class NullFormatter(Formatter):
+ """
+ Output the text unchanged without any formatting.
+ """
+ name = 'Text only'
+ aliases = ['text', 'null']
+ filenames = ['*.txt']
+
+ def format(self, tokensource, outfile):
+ enc = self.encoding
+ for ttype, value in tokensource:
+ if enc:
+ outfile.write(value.encode(enc))
+ else:
+ outfile.write(value)
+
+
+class RawTokenFormatter(Formatter):
+ r"""
+ Format tokens as a raw representation for storing token streams.
+
+ The format is ``tokentype<TAB>repr(tokenstring)\n``. The output can later
+ be converted to a token stream with the `RawTokenLexer`, described in the
+ :doc:`lexer list <lexers>`.
+
+ Only two options are accepted:
+
+ `compress`
+ If set to ``'gz'`` or ``'bz2'``, compress the output with the given
+ compression algorithm after encoding (default: ``''``).
+ `error_color`
+ If set to a color name, highlight error tokens using that color. If
+ set but with no value, defaults to ``'red'``.
+
+ .. versionadded:: 0.11
+
+ """
+ name = 'Raw tokens'
+ aliases = ['raw', 'tokens']
+ filenames = ['*.raw']
+
+ unicodeoutput = False
+
+ def __init__(self, **options):
+ Formatter.__init__(self, **options)
+ # We ignore self.encoding if it is set, since it gets set for lexer
+ # and formatter if given with -Oencoding on the command line.
+ # The RawTokenFormatter outputs only ASCII. Override here.
+ self.encoding = 'ascii' # let pygments.format() do the right thing
+ self.compress = get_choice_opt(options, 'compress',
+ ['', 'none', 'gz', 'bz2'], '')
+ self.error_color = options.get('error_color', None)
+ if self.error_color is True:
+ self.error_color = 'red'
+ if self.error_color is not None:
+ try:
+ colorize(self.error_color, '')
+ except KeyError:
+ raise ValueError("Invalid color %r specified" %
+ self.error_color)
+
+ def format(self, tokensource, outfile):
+ try:
+ outfile.write(b'')
+ except TypeError:
+ raise TypeError('The raw tokens formatter needs a binary '
+ 'output file')
+ if self.compress == 'gz':
+ import gzip
+ outfile = gzip.GzipFile('', 'wb', 9, outfile)
+
+ write = outfile.write
+ flush = outfile.close
+ elif self.compress == 'bz2':
+ import bz2
+ compressor = bz2.BZ2Compressor(9)
+
+ def write(text):
+ outfile.write(compressor.compress(text))
+
+ def flush():
+ outfile.write(compressor.flush())
+ outfile.flush()
+ else:
+ write = outfile.write
+ flush = outfile.flush
+
+ if self.error_color:
+ for ttype, value in tokensource:
+ line = b"%r\t%r\n" % (ttype, value)
+ if ttype is Token.Error:
+ write(colorize(self.error_color, line))
+ else:
+ write(line)
+ else:
+ for ttype, value in tokensource:
+ write(b"%r\t%r\n" % (ttype, value))
+ flush()
+
+
+TESTCASE_BEFORE = '''\
+ def testNeedsName(lexer):
+ fragment = %r
+ tokens = [
+'''
+TESTCASE_AFTER = '''\
+ ]
+ assert list(lexer.get_tokens(fragment)) == tokens
+'''
+
+
+class TestcaseFormatter(Formatter):
+ """
+ Format tokens as appropriate for a new testcase.
+
+ .. versionadded:: 2.0
+ """
+ name = 'Testcase'
+ aliases = ['testcase']
+
+ def __init__(self, **options):
+ Formatter.__init__(self, **options)
+ if self.encoding is not None and self.encoding != 'utf-8':
+ raise ValueError("Only None and utf-8 are allowed encodings.")
+
+ def format(self, tokensource, outfile):
+ indentation = ' ' * 12
+ rawbuf = []
+ outbuf = []
+ for ttype, value in tokensource:
+ rawbuf.append(value)
+ outbuf.append('%s(%s, %r),\n' % (indentation, ttype, value))
+
+ before = TESTCASE_BEFORE % (''.join(rawbuf),)
+ during = ''.join(outbuf)
+ after = TESTCASE_AFTER
+ if self.encoding is None:
+ outfile.write(before + during + after)
+ else:
+ outfile.write(before.encode('utf-8'))
+ outfile.write(during.encode('utf-8'))
+ outfile.write(after.encode('utf-8'))
+ outfile.flush()
diff --git a/src/pip/_vendor/pygments/formatters/pangomarkup.py b/src/pip/_vendor/pygments/formatters/pangomarkup.py
new file mode 100644
index 000000000..bd00866b8
--- /dev/null
+++ b/src/pip/_vendor/pygments/formatters/pangomarkup.py
@@ -0,0 +1,83 @@
+"""
+ pygments.formatters.pangomarkup
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Formatter for Pango markup output.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from pip._vendor.pygments.formatter import Formatter
+
+
+__all__ = ['PangoMarkupFormatter']
+
+
+_escape_table = {
+ ord('&'): '&amp;',
+ ord('<'): '&lt;',
+}
+
+
+def escape_special_chars(text, table=_escape_table):
+ """Escape & and < for Pango Markup."""
+ return text.translate(table)
+
+
+class PangoMarkupFormatter(Formatter):
+ """
+ Format tokens as Pango Markup code. It can then be rendered to an SVG.
+
+ .. versionadded:: 2.9
+ """
+
+ name = 'Pango Markup'
+ aliases = ['pango', 'pangomarkup']
+ filenames = []
+
+ def __init__(self, **options):
+ Formatter.__init__(self, **options)
+
+ self.styles = {}
+
+ for token, style in self.style:
+ start = ''
+ end = ''
+ if style['color']:
+ start += '<span fgcolor="#%s">' % style['color']
+ end = '</span>' + end
+ if style['bold']:
+ start += '<b>'
+ end = '</b>' + end
+ if style['italic']:
+ start += '<i>'
+ end = '</i>' + end
+ if style['underline']:
+ start += '<u>'
+ end = '</u>' + end
+ self.styles[token] = (start, end)
+
+ def format_unencoded(self, tokensource, outfile):
+ lastval = ''
+ lasttype = None
+
+ outfile.write('<tt>')
+
+ for ttype, value in tokensource:
+ while ttype not in self.styles:
+ ttype = ttype.parent
+ if ttype == lasttype:
+ lastval += escape_special_chars(value)
+ else:
+ if lastval:
+ stylebegin, styleend = self.styles[lasttype]
+ outfile.write(stylebegin + lastval + styleend)
+ lastval = escape_special_chars(value)
+ lasttype = ttype
+
+ if lastval:
+ stylebegin, styleend = self.styles[lasttype]
+ outfile.write(stylebegin + lastval + styleend)
+
+ outfile.write('</tt>')
diff --git a/src/pip/_vendor/pygments/formatters/rtf.py b/src/pip/_vendor/pygments/formatters/rtf.py
new file mode 100644
index 000000000..4114d1688
--- /dev/null
+++ b/src/pip/_vendor/pygments/formatters/rtf.py
@@ -0,0 +1,146 @@
+"""
+ pygments.formatters.rtf
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+ A formatter that generates RTF files.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.util import get_int_opt, surrogatepair
+
+
+__all__ = ['RtfFormatter']
+
+
+class RtfFormatter(Formatter):
+ """
+ Format tokens as RTF markup. This formatter automatically outputs full RTF
+ documents with color information and other useful stuff. Perfect for Copy and
+ Paste into Microsoft(R) Word(R) documents.
+
+ Please note that ``encoding`` and ``outencoding`` options are ignored.
+ The RTF format is ASCII natively, but handles unicode characters correctly
+ thanks to escape sequences.
+
+ .. versionadded:: 0.6
+
+ Additional options accepted:
+
+ `style`
+ The style to use, can be a string or a Style subclass (default:
+ ``'default'``).
+
+ `fontface`
+ The used font family, for example ``Bitstream Vera Sans``. Defaults to
+ some generic font which is supposed to have fixed width.
+
+ `fontsize`
+ Size of the font used. Size is specified in half points. The
+ default is 24 half-points, giving a size 12 font.
+
+ .. versionadded:: 2.0
+ """
+ name = 'RTF'
+ aliases = ['rtf']
+ filenames = ['*.rtf']
+
+ def __init__(self, **options):
+ r"""
+ Additional options accepted:
+
+ ``fontface``
+ Name of the font used. Could for example be ``'Courier New'``
+ to further specify the default which is ``'\fmodern'``. The RTF
+ specification claims that ``\fmodern`` are "Fixed-pitch serif
+ and sans serif fonts". Hope every RTF implementation thinks
+ the same about modern...
+
+ """
+ Formatter.__init__(self, **options)
+ self.fontface = options.get('fontface') or ''
+ self.fontsize = get_int_opt(options, 'fontsize', 0)
+
+ def _escape(self, text):
+ return text.replace('\\', '\\\\') \
+ .replace('{', '\\{') \
+ .replace('}', '\\}')
+
+ def _escape_text(self, text):
+ # empty strings, should give a small performance improvement
+ if not text:
+ return ''
+
+ # escape text
+ text = self._escape(text)
+
+ buf = []
+ for c in text:
+ cn = ord(c)
+ if cn < (2**7):
+ # ASCII character
+ buf.append(str(c))
+ elif (2**7) <= cn < (2**16):
+ # single unicode escape sequence
+ buf.append('{\\u%d}' % cn)
+ elif (2**16) <= cn:
+ # RTF limits unicode to 16 bits.
+ # Force surrogate pairs
+ buf.append('{\\u%d}{\\u%d}' % surrogatepair(cn))
+
+ return ''.join(buf).replace('\n', '\\par\n')
+
+ def format_unencoded(self, tokensource, outfile):
+ # rtf 1.8 header
+ outfile.write('{\\rtf1\\ansi\\uc0\\deff0'
+ '{\\fonttbl{\\f0\\fmodern\\fprq1\\fcharset0%s;}}'
+ '{\\colortbl;' % (self.fontface and
+ ' ' + self._escape(self.fontface) or
+ ''))
+
+ # convert colors and save them in a mapping to access them later.
+ color_mapping = {}
+ offset = 1
+ for _, style in self.style:
+ for color in style['color'], style['bgcolor'], style['border']:
+ if color and color not in color_mapping:
+ color_mapping[color] = offset
+ outfile.write('\\red%d\\green%d\\blue%d;' % (
+ int(color[0:2], 16),
+ int(color[2:4], 16),
+ int(color[4:6], 16)
+ ))
+ offset += 1
+ outfile.write('}\\f0 ')
+ if self.fontsize:
+ outfile.write('\\fs%d' % self.fontsize)
+
+ # highlight stream
+ for ttype, value in tokensource:
+ while not self.style.styles_token(ttype) and ttype.parent:
+ ttype = ttype.parent
+ style = self.style.style_for_token(ttype)
+ buf = []
+ if style['bgcolor']:
+ buf.append('\\cb%d' % color_mapping[style['bgcolor']])
+ if style['color']:
+ buf.append('\\cf%d' % color_mapping[style['color']])
+ if style['bold']:
+ buf.append('\\b')
+ if style['italic']:
+ buf.append('\\i')
+ if style['underline']:
+ buf.append('\\ul')
+ if style['border']:
+ buf.append('\\chbrdr\\chcfpat%d' %
+ color_mapping[style['border']])
+ start = ''.join(buf)
+ if start:
+ outfile.write('{%s ' % start)
+ outfile.write(self._escape_text(value))
+ if start:
+ outfile.write('}')
+
+ outfile.write('}')
diff --git a/src/pip/_vendor/pygments/formatters/svg.py b/src/pip/_vendor/pygments/formatters/svg.py
new file mode 100644
index 000000000..075150a4b
--- /dev/null
+++ b/src/pip/_vendor/pygments/formatters/svg.py
@@ -0,0 +1,188 @@
+"""
+ pygments.formatters.svg
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+ Formatter for SVG output.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.token import Comment
+from pip._vendor.pygments.util import get_bool_opt, get_int_opt
+
+__all__ = ['SvgFormatter']
+
+
+def escape_html(text):
+ """Escape &, <, > as well as single and double quotes for HTML."""
+ return text.replace('&', '&amp;'). \
+ replace('<', '&lt;'). \
+ replace('>', '&gt;'). \
+ replace('"', '&quot;'). \
+ replace("'", '&#39;')
+
+
+class2style = {}
+
+class SvgFormatter(Formatter):
+ """
+ Format tokens as an SVG graphics file. This formatter is still experimental.
+ Each line of code is a ``<text>`` element with explicit ``x`` and ``y``
+ coordinates containing ``<tspan>`` elements with the individual token styles.
+
+ By default, this formatter outputs a full SVG document including doctype
+ declaration and the ``<svg>`` root element.
+
+ .. versionadded:: 0.9
+
+ Additional options accepted:
+
+ `nowrap`
+ Don't wrap the SVG ``<text>`` elements in ``<svg><g>`` elements and
+ don't add a XML declaration and a doctype. If true, the `fontfamily`
+ and `fontsize` options are ignored. Defaults to ``False``.
+
+ `fontfamily`
+ The value to give the wrapping ``<g>`` element's ``font-family``
+ attribute, defaults to ``"monospace"``.
+
+ `fontsize`
+ The value to give the wrapping ``<g>`` element's ``font-size``
+ attribute, defaults to ``"14px"``.
+
+ `linenos`
+ If ``True``, add line numbers (default: ``False``).
+
+ `linenostart`
+ The line number for the first line (default: ``1``).
+
+ `linenostep`
+ If set to a number n > 1, only every nth line number is printed.
+
+ `linenowidth`
+ Maximum width devoted to line numbers (default: ``3*ystep``, sufficient
+ for up to 4-digit line numbers. Increase width for longer code blocks).
+
+ `xoffset`
+ Starting offset in X direction, defaults to ``0``.
+
+ `yoffset`
+ Starting offset in Y direction, defaults to the font size if it is given
+ in pixels, or ``20`` else. (This is necessary since text coordinates
+ refer to the text baseline, not the top edge.)
+
+ `ystep`
+ Offset to add to the Y coordinate for each subsequent line. This should
+ roughly be the text size plus 5. It defaults to that value if the text
+ size is given in pixels, or ``25`` else.
+
+ `spacehack`
+ Convert spaces in the source to ``&#160;``, which are non-breaking
+ spaces. SVG provides the ``xml:space`` attribute to control how
+ whitespace inside tags is handled, in theory, the ``preserve`` value
+ could be used to keep all whitespace as-is. However, many current SVG
+ viewers don't obey that rule, so this option is provided as a workaround
+ and defaults to ``True``.
+ """
+ name = 'SVG'
+ aliases = ['svg']
+ filenames = ['*.svg']
+
+ def __init__(self, **options):
+ Formatter.__init__(self, **options)
+ self.nowrap = get_bool_opt(options, 'nowrap', False)
+ self.fontfamily = options.get('fontfamily', 'monospace')
+ self.fontsize = options.get('fontsize', '14px')
+ self.xoffset = get_int_opt(options, 'xoffset', 0)
+ fs = self.fontsize.strip()
+ if fs.endswith('px'): fs = fs[:-2].strip()
+ try:
+ int_fs = int(fs)
+ except:
+ int_fs = 20
+ self.yoffset = get_int_opt(options, 'yoffset', int_fs)
+ self.ystep = get_int_opt(options, 'ystep', int_fs + 5)
+ self.spacehack = get_bool_opt(options, 'spacehack', True)
+ self.linenos = get_bool_opt(options,'linenos',False)
+ self.linenostart = get_int_opt(options,'linenostart',1)
+ self.linenostep = get_int_opt(options,'linenostep',1)
+ self.linenowidth = get_int_opt(options,'linenowidth', 3*self.ystep)
+ self._stylecache = {}
+
+ def format_unencoded(self, tokensource, outfile):
+ """
+ Format ``tokensource``, an iterable of ``(tokentype, tokenstring)``
+ tuples and write it into ``outfile``.
+
+ For our implementation we put all lines in their own 'line group'.
+ """
+ x = self.xoffset
+ y = self.yoffset
+ if not self.nowrap:
+ if self.encoding:
+ outfile.write('<?xml version="1.0" encoding="%s"?>\n' %
+ self.encoding)
+ else:
+ outfile.write('<?xml version="1.0"?>\n')
+ outfile.write('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" '
+ '"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/'
+ 'svg10.dtd">\n')
+ outfile.write('<svg xmlns="http://www.w3.org/2000/svg">\n')
+ outfile.write('<g font-family="%s" font-size="%s">\n' %
+ (self.fontfamily, self.fontsize))
+
+ counter = self.linenostart
+ counter_step = self.linenostep
+ counter_style = self._get_style(Comment)
+ line_x = x
+
+ if self.linenos:
+ if counter % counter_step == 0:
+ outfile.write('<text x="%s" y="%s" %s text-anchor="end">%s</text>' %
+ (x+self.linenowidth,y,counter_style,counter))
+ line_x += self.linenowidth + self.ystep
+ counter += 1
+
+ outfile.write('<text x="%s" y="%s" xml:space="preserve">' % (line_x, y))
+ for ttype, value in tokensource:
+ style = self._get_style(ttype)
+ tspan = style and '<tspan' + style + '>' or ''
+ tspanend = tspan and '</tspan>' or ''
+ value = escape_html(value)
+ if self.spacehack:
+ value = value.expandtabs().replace(' ', '&#160;')
+ parts = value.split('\n')
+ for part in parts[:-1]:
+ outfile.write(tspan + part + tspanend)
+ y += self.ystep
+ outfile.write('</text>\n')
+ if self.linenos and counter % counter_step == 0:
+ outfile.write('<text x="%s" y="%s" text-anchor="end" %s>%s</text>' %
+ (x+self.linenowidth,y,counter_style,counter))
+
+ counter += 1
+ outfile.write('<text x="%s" y="%s" ' 'xml:space="preserve">' % (line_x,y))
+ outfile.write(tspan + parts[-1] + tspanend)
+ outfile.write('</text>')
+
+ if not self.nowrap:
+ outfile.write('</g></svg>\n')
+
+ def _get_style(self, tokentype):
+ if tokentype in self._stylecache:
+ return self._stylecache[tokentype]
+ otokentype = tokentype
+ while not self.style.styles_token(tokentype):
+ tokentype = tokentype.parent
+ value = self.style.style_for_token(tokentype)
+ result = ''
+ if value['color']:
+ result = ' fill="#' + value['color'] + '"'
+ if value['bold']:
+ result += ' font-weight="bold"'
+ if value['italic']:
+ result += ' font-style="italic"'
+ self._stylecache[otokentype] = result
+ return result
diff --git a/src/pip/_vendor/pygments/formatters/terminal.py b/src/pip/_vendor/pygments/formatters/terminal.py
new file mode 100644
index 000000000..e0bda16a2
--- /dev/null
+++ b/src/pip/_vendor/pygments/formatters/terminal.py
@@ -0,0 +1,127 @@
+"""
+ pygments.formatters.terminal
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Formatter for terminal output with ANSI sequences.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.token import Keyword, Name, Comment, String, Error, \
+ Number, Operator, Generic, Token, Whitespace
+from pip._vendor.pygments.console import ansiformat
+from pip._vendor.pygments.util import get_choice_opt
+
+
+__all__ = ['TerminalFormatter']
+
+
+#: Map token types to a tuple of color values for light and dark
+#: backgrounds.
+TERMINAL_COLORS = {
+ Token: ('', ''),
+
+ Whitespace: ('gray', 'brightblack'),
+ Comment: ('gray', 'brightblack'),
+ Comment.Preproc: ('cyan', 'brightcyan'),
+ Keyword: ('blue', 'brightblue'),
+ Keyword.Type: ('cyan', 'brightcyan'),
+ Operator.Word: ('magenta', 'brightmagenta'),
+ Name.Builtin: ('cyan', 'brightcyan'),
+ Name.Function: ('green', 'brightgreen'),
+ Name.Namespace: ('_cyan_', '_brightcyan_'),
+ Name.Class: ('_green_', '_brightgreen_'),
+ Name.Exception: ('cyan', 'brightcyan'),
+ Name.Decorator: ('brightblack', 'gray'),
+ Name.Variable: ('red', 'brightred'),
+ Name.Constant: ('red', 'brightred'),
+ Name.Attribute: ('cyan', 'brightcyan'),
+ Name.Tag: ('brightblue', 'brightblue'),
+ String: ('yellow', 'yellow'),
+ Number: ('blue', 'brightblue'),
+
+ Generic.Deleted: ('brightred', 'brightred'),
+ Generic.Inserted: ('green', 'brightgreen'),
+ Generic.Heading: ('**', '**'),
+ Generic.Subheading: ('*magenta*', '*brightmagenta*'),
+ Generic.Prompt: ('**', '**'),
+ Generic.Error: ('brightred', 'brightred'),
+
+ Error: ('_brightred_', '_brightred_'),
+}
+
+
+class TerminalFormatter(Formatter):
+ r"""
+ Format tokens with ANSI color sequences, for output in a text console.
+ Color sequences are terminated at newlines, so that paging the output
+ works correctly.
+
+ The `get_style_defs()` method doesn't do anything special since there is
+ no support for common styles.
+
+ Options accepted:
+
+ `bg`
+ Set to ``"light"`` or ``"dark"`` depending on the terminal's background
+ (default: ``"light"``).
+
+ `colorscheme`
+ A dictionary mapping token types to (lightbg, darkbg) color names or
+ ``None`` (default: ``None`` = use builtin colorscheme).
+
+ `linenos`
+ Set to ``True`` to have line numbers on the terminal output as well
+ (default: ``False`` = no line numbers).
+ """
+ name = 'Terminal'
+ aliases = ['terminal', 'console']
+ filenames = []
+
+ def __init__(self, **options):
+ Formatter.__init__(self, **options)
+ self.darkbg = get_choice_opt(options, 'bg',
+ ['light', 'dark'], 'light') == 'dark'
+ self.colorscheme = options.get('colorscheme', None) or TERMINAL_COLORS
+ self.linenos = options.get('linenos', False)
+ self._lineno = 0
+
+ def format(self, tokensource, outfile):
+ return Formatter.format(self, tokensource, outfile)
+
+ def _write_lineno(self, outfile):
+ self._lineno += 1
+ outfile.write("%s%04d: " % (self._lineno != 1 and '\n' or '', self._lineno))
+
+ def _get_color(self, ttype):
+ # self.colorscheme is a dict containing usually generic types, so we
+ # have to walk the tree of dots. The base Token type must be a key,
+ # even if it's empty string, as in the default above.
+ colors = self.colorscheme.get(ttype)
+ while colors is None:
+ ttype = ttype.parent
+ colors = self.colorscheme.get(ttype)
+ return colors[self.darkbg]
+
+ def format_unencoded(self, tokensource, outfile):
+ if self.linenos:
+ self._write_lineno(outfile)
+
+ for ttype, value in tokensource:
+ color = self._get_color(ttype)
+
+ for line in value.splitlines(True):
+ if color:
+ outfile.write(ansiformat(color, line.rstrip('\n')))
+ else:
+ outfile.write(line.rstrip('\n'))
+ if line.endswith('\n'):
+ if self.linenos:
+ self._write_lineno(outfile)
+ else:
+ outfile.write('\n')
+
+ if self.linenos:
+ outfile.write("\n")
diff --git a/src/pip/_vendor/pygments/formatters/terminal256.py b/src/pip/_vendor/pygments/formatters/terminal256.py
new file mode 100644
index 000000000..201b3c328
--- /dev/null
+++ b/src/pip/_vendor/pygments/formatters/terminal256.py
@@ -0,0 +1,338 @@
+"""
+ pygments.formatters.terminal256
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Formatter for 256-color terminal output with ANSI sequences.
+
+ RGB-to-XTERM color conversion routines adapted from xterm256-conv
+ tool (http://frexx.de/xterm-256-notes/data/xterm256-conv2.tar.bz2)
+ by Wolfgang Frisch.
+
+ Formatter version 1.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+# TODO:
+# - Options to map style's bold/underline/italic/border attributes
+# to some ANSI attrbutes (something like 'italic=underline')
+# - An option to output "style RGB to xterm RGB/index" conversion table
+# - An option to indicate that we are running in "reverse background"
+# xterm. This means that default colors are white-on-black, not
+# black-on-while, so colors like "white background" need to be converted
+# to "white background, black foreground", etc...
+
+from pip._vendor.pygments.formatter import Formatter
+from pip._vendor.pygments.console import codes
+from pip._vendor.pygments.style import ansicolors
+
+
+__all__ = ['Terminal256Formatter', 'TerminalTrueColorFormatter']
+
+
+class EscapeSequence:
+ def __init__(self, fg=None, bg=None, bold=False, underline=False, italic=False):
+ self.fg = fg
+ self.bg = bg
+ self.bold = bold
+ self.underline = underline
+ self.italic = italic
+
+ def escape(self, attrs):
+ if len(attrs):
+ return "\x1b[" + ";".join(attrs) + "m"
+ return ""
+
+ def color_string(self):
+ attrs = []
+ if self.fg is not None:
+ if self.fg in ansicolors:
+ esc = codes[self.fg.replace('ansi','')]
+ if ';01m' in esc:
+ self.bold = True
+ # extract fg color code.
+ attrs.append(esc[2:4])
+ else:
+ attrs.extend(("38", "5", "%i" % self.fg))
+ if self.bg is not None:
+ if self.bg in ansicolors:
+ esc = codes[self.bg.replace('ansi','')]
+ # extract fg color code, add 10 for bg.
+ attrs.append(str(int(esc[2:4])+10))
+ else:
+ attrs.extend(("48", "5", "%i" % self.bg))
+ if self.bold:
+ attrs.append("01")
+ if self.underline:
+ attrs.append("04")
+ if self.italic:
+ attrs.append("03")
+ return self.escape(attrs)
+
+ def true_color_string(self):
+ attrs = []
+ if self.fg:
+ attrs.extend(("38", "2", str(self.fg[0]), str(self.fg[1]), str(self.fg[2])))
+ if self.bg:
+ attrs.extend(("48", "2", str(self.bg[0]), str(self.bg[1]), str(self.bg[2])))
+ if self.bold:
+ attrs.append("01")
+ if self.underline:
+ attrs.append("04")
+ if self.italic:
+ attrs.append("03")
+ return self.escape(attrs)
+
+ def reset_string(self):
+ attrs = []
+ if self.fg is not None:
+ attrs.append("39")
+ if self.bg is not None:
+ attrs.append("49")
+ if self.bold or self.underline or self.italic:
+ attrs.append("00")
+ return self.escape(attrs)
+
+
+class Terminal256Formatter(Formatter):
+ """
+ Format tokens with ANSI color sequences, for output in a 256-color
+ terminal or console. Like in `TerminalFormatter` color sequences
+ are terminated at newlines, so that paging the output works correctly.
+
+ The formatter takes colors from a style defined by the `style` option
+ and converts them to nearest ANSI 256-color escape sequences. Bold and
+ underline attributes from the style are preserved (and displayed).
+
+ .. versionadded:: 0.9
+
+ .. versionchanged:: 2.2
+ If the used style defines foreground colors in the form ``#ansi*``, then
+ `Terminal256Formatter` will map these to non extended foreground color.
+ See :ref:`AnsiTerminalStyle` for more information.
+
+ .. versionchanged:: 2.4
+ The ANSI color names have been updated with names that are easier to
+ understand and align with colornames of other projects and terminals.
+ See :ref:`this table <new-ansi-color-names>` for more information.
+
+
+ Options accepted:
+
+ `style`
+ The style to use, can be a string or a Style subclass (default:
+ ``'default'``).
+
+ `linenos`
+ Set to ``True`` to have line numbers on the terminal output as well
+ (default: ``False`` = no line numbers).
+ """
+ name = 'Terminal256'
+ aliases = ['terminal256', 'console256', '256']
+ filenames = []
+
+ def __init__(self, **options):
+ Formatter.__init__(self, **options)
+
+ self.xterm_colors = []
+ self.best_match = {}
+ self.style_string = {}
+
+ self.usebold = 'nobold' not in options
+ self.useunderline = 'nounderline' not in options
+ self.useitalic = 'noitalic' not in options
+
+ self._build_color_table() # build an RGB-to-256 color conversion table
+ self._setup_styles() # convert selected style's colors to term. colors
+
+ self.linenos = options.get('linenos', False)
+ self._lineno = 0
+
+ def _build_color_table(self):
+ # colors 0..15: 16 basic colors
+
+ self.xterm_colors.append((0x00, 0x00, 0x00)) # 0
+ self.xterm_colors.append((0xcd, 0x00, 0x00)) # 1
+ self.xterm_colors.append((0x00, 0xcd, 0x00)) # 2
+ self.xterm_colors.append((0xcd, 0xcd, 0x00)) # 3
+ self.xterm_colors.append((0x00, 0x00, 0xee)) # 4
+ self.xterm_colors.append((0xcd, 0x00, 0xcd)) # 5
+ self.xterm_colors.append((0x00, 0xcd, 0xcd)) # 6
+ self.xterm_colors.append((0xe5, 0xe5, 0xe5)) # 7
+ self.xterm_colors.append((0x7f, 0x7f, 0x7f)) # 8
+ self.xterm_colors.append((0xff, 0x00, 0x00)) # 9
+ self.xterm_colors.append((0x00, 0xff, 0x00)) # 10
+ self.xterm_colors.append((0xff, 0xff, 0x00)) # 11
+ self.xterm_colors.append((0x5c, 0x5c, 0xff)) # 12
+ self.xterm_colors.append((0xff, 0x00, 0xff)) # 13
+ self.xterm_colors.append((0x00, 0xff, 0xff)) # 14
+ self.xterm_colors.append((0xff, 0xff, 0xff)) # 15
+
+ # colors 16..232: the 6x6x6 color cube
+
+ valuerange = (0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff)
+
+ for i in range(217):
+ r = valuerange[(i // 36) % 6]
+ g = valuerange[(i // 6) % 6]
+ b = valuerange[i % 6]
+ self.xterm_colors.append((r, g, b))
+
+ # colors 233..253: grayscale
+
+ for i in range(1, 22):
+ v = 8 + i * 10
+ self.xterm_colors.append((v, v, v))
+
+ def _closest_color(self, r, g, b):
+ distance = 257*257*3 # "infinity" (>distance from #000000 to #ffffff)
+ match = 0
+
+ for i in range(0, 254):
+ values = self.xterm_colors[i]
+
+ rd = r - values[0]
+ gd = g - values[1]
+ bd = b - values[2]
+ d = rd*rd + gd*gd + bd*bd
+
+ if d < distance:
+ match = i
+ distance = d
+ return match
+
+ def _color_index(self, color):
+ index = self.best_match.get(color, None)
+ if color in ansicolors:
+ # strip the `ansi/#ansi` part and look up code
+ index = color
+ self.best_match[color] = index
+ if index is None:
+ try:
+ rgb = int(str(color), 16)
+ except ValueError:
+ rgb = 0
+
+ r = (rgb >> 16) & 0xff
+ g = (rgb >> 8) & 0xff
+ b = rgb & 0xff
+ index = self._closest_color(r, g, b)
+ self.best_match[color] = index
+ return index
+
+ def _setup_styles(self):
+ for ttype, ndef in self.style:
+ escape = EscapeSequence()
+ # get foreground from ansicolor if set
+ if ndef['ansicolor']:
+ escape.fg = self._color_index(ndef['ansicolor'])
+ elif ndef['color']:
+ escape.fg = self._color_index(ndef['color'])
+ if ndef['bgansicolor']:
+ escape.bg = self._color_index(ndef['bgansicolor'])
+ elif ndef['bgcolor']:
+ escape.bg = self._color_index(ndef['bgcolor'])
+ if self.usebold and ndef['bold']:
+ escape.bold = True
+ if self.useunderline and ndef['underline']:
+ escape.underline = True
+ if self.useitalic and ndef['italic']:
+ escape.italic = True
+ self.style_string[str(ttype)] = (escape.color_string(),
+ escape.reset_string())
+
+ def _write_lineno(self, outfile):
+ self._lineno += 1
+ outfile.write("%s%04d: " % (self._lineno != 1 and '\n' or '', self._lineno))
+
+ def format(self, tokensource, outfile):
+ return Formatter.format(self, tokensource, outfile)
+
+ def format_unencoded(self, tokensource, outfile):
+ if self.linenos:
+ self._write_lineno(outfile)
+
+ for ttype, value in tokensource:
+ not_found = True
+ while ttype and not_found:
+ try:
+ # outfile.write( "<" + str(ttype) + ">" )
+ on, off = self.style_string[str(ttype)]
+
+ # Like TerminalFormatter, add "reset colors" escape sequence
+ # on newline.
+ spl = value.split('\n')
+ for line in spl[:-1]:
+ if line:
+ outfile.write(on + line + off)
+ if self.linenos:
+ self._write_lineno(outfile)
+ else:
+ outfile.write('\n')
+
+ if spl[-1]:
+ outfile.write(on + spl[-1] + off)
+
+ not_found = False
+ # outfile.write( '#' + str(ttype) + '#' )
+
+ except KeyError:
+ # ottype = ttype
+ ttype = ttype.parent
+ # outfile.write( '!' + str(ottype) + '->' + str(ttype) + '!' )
+
+ if not_found:
+ outfile.write(value)
+
+ if self.linenos:
+ outfile.write("\n")
+
+
+
+class TerminalTrueColorFormatter(Terminal256Formatter):
+ r"""
+ Format tokens with ANSI color sequences, for output in a true-color
+ terminal or console. Like in `TerminalFormatter` color sequences
+ are terminated at newlines, so that paging the output works correctly.
+
+ .. versionadded:: 2.1
+
+ Options accepted:
+
+ `style`
+ The style to use, can be a string or a Style subclass (default:
+ ``'default'``).
+ """
+ name = 'TerminalTrueColor'
+ aliases = ['terminal16m', 'console16m', '16m']
+ filenames = []
+
+ def _build_color_table(self):
+ pass
+
+ def _color_tuple(self, color):
+ try:
+ rgb = int(str(color), 16)
+ except ValueError:
+ return None
+ r = (rgb >> 16) & 0xff
+ g = (rgb >> 8) & 0xff
+ b = rgb & 0xff
+ return (r, g, b)
+
+ def _setup_styles(self):
+ for ttype, ndef in self.style:
+ escape = EscapeSequence()
+ if ndef['color']:
+ escape.fg = self._color_tuple(ndef['color'])
+ if ndef['bgcolor']:
+ escape.bg = self._color_tuple(ndef['bgcolor'])
+ if self.usebold and ndef['bold']:
+ escape.bold = True
+ if self.useunderline and ndef['underline']:
+ escape.underline = True
+ if self.useitalic and ndef['italic']:
+ escape.italic = True
+ self.style_string[str(ttype)] = (escape.true_color_string(),
+ escape.reset_string())
diff --git a/src/pip/_vendor/pygments/lexer.py b/src/pip/_vendor/pygments/lexer.py
new file mode 100644
index 000000000..ec7f4de32
--- /dev/null
+++ b/src/pip/_vendor/pygments/lexer.py
@@ -0,0 +1,882 @@
+"""
+ pygments.lexer
+ ~~~~~~~~~~~~~~
+
+ Base lexer classes.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import re
+import sys
+import time
+
+from pip._vendor.pygments.filter import apply_filters, Filter
+from pip._vendor.pygments.filters import get_filter_by_name
+from pip._vendor.pygments.token import Error, Text, Other, _TokenType
+from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt, \
+ make_analysator, Future, guess_decode
+from pip._vendor.pygments.regexopt import regex_opt
+
+__all__ = ['Lexer', 'RegexLexer', 'ExtendedRegexLexer', 'DelegatingLexer',
+ 'LexerContext', 'include', 'inherit', 'bygroups', 'using', 'this',
+ 'default', 'words']
+
+
+_encoding_map = [(b'\xef\xbb\xbf', 'utf-8'),
+ (b'\xff\xfe\0\0', 'utf-32'),
+ (b'\0\0\xfe\xff', 'utf-32be'),
+ (b'\xff\xfe', 'utf-16'),
+ (b'\xfe\xff', 'utf-16be')]
+
+_default_analyse = staticmethod(lambda x: 0.0)
+
+
+class LexerMeta(type):
+ """
+ This metaclass automagically converts ``analyse_text`` methods into
+ static methods which always return float values.
+ """
+
+ def __new__(mcs, name, bases, d):
+ if 'analyse_text' in d:
+ d['analyse_text'] = make_analysator(d['analyse_text'])
+ return type.__new__(mcs, name, bases, d)
+
+
+class Lexer(metaclass=LexerMeta):
+ """
+ Lexer for a specific language.
+
+ Basic options recognized:
+ ``stripnl``
+ Strip leading and trailing newlines from the input (default: True).
+ ``stripall``
+ Strip all leading and trailing whitespace from the input
+ (default: False).
+ ``ensurenl``
+ Make sure that the input ends with a newline (default: True). This
+ is required for some lexers that consume input linewise.
+
+ .. versionadded:: 1.3
+
+ ``tabsize``
+ If given and greater than 0, expand tabs in the input (default: 0).
+ ``encoding``
+ If given, must be an encoding name. This encoding will be used to
+ convert the input string to Unicode, if it is not already a Unicode
+ string (default: ``'guess'``, which uses a simple UTF-8 / Locale /
+ Latin1 detection. Can also be ``'chardet'`` to use the chardet
+ library, if it is installed.
+ ``inencoding``
+ Overrides the ``encoding`` if given.
+ """
+
+ #: Name of the lexer
+ name = None
+
+ #: URL of the language specification/definition
+ url = None
+
+ #: Shortcuts for the lexer
+ aliases = []
+
+ #: File name globs
+ filenames = []
+
+ #: Secondary file name globs
+ alias_filenames = []
+
+ #: MIME types
+ mimetypes = []
+
+ #: Priority, should multiple lexers match and no content is provided
+ priority = 0
+
+ def __init__(self, **options):
+ self.options = options
+ self.stripnl = get_bool_opt(options, 'stripnl', True)
+ self.stripall = get_bool_opt(options, 'stripall', False)
+ self.ensurenl = get_bool_opt(options, 'ensurenl', True)
+ self.tabsize = get_int_opt(options, 'tabsize', 0)
+ self.encoding = options.get('encoding', 'guess')
+ self.encoding = options.get('inencoding') or self.encoding
+ self.filters = []
+ for filter_ in get_list_opt(options, 'filters', ()):
+ self.add_filter(filter_)
+
+ def __repr__(self):
+ if self.options:
+ return '<pygments.lexers.%s with %r>' % (self.__class__.__name__,
+ self.options)
+ else:
+ return '<pygments.lexers.%s>' % self.__class__.__name__
+
+ def add_filter(self, filter_, **options):
+ """
+ Add a new stream filter to this lexer.
+ """
+ if not isinstance(filter_, Filter):
+ filter_ = get_filter_by_name(filter_, **options)
+ self.filters.append(filter_)
+
+ def analyse_text(text):
+ """
+ Has to return a float between ``0`` and ``1`` that indicates
+ if a lexer wants to highlight this text. Used by ``guess_lexer``.
+ If this method returns ``0`` it won't highlight it in any case, if
+ it returns ``1`` highlighting with this lexer is guaranteed.
+
+ The `LexerMeta` metaclass automatically wraps this function so
+ that it works like a static method (no ``self`` or ``cls``
+ parameter) and the return value is automatically converted to
+ `float`. If the return value is an object that is boolean `False`
+ it's the same as if the return values was ``0.0``.
+ """
+
+ def get_tokens(self, text, unfiltered=False):
+ """
+ Return an iterable of (tokentype, value) pairs generated from
+ `text`. If `unfiltered` is set to `True`, the filtering mechanism
+ is bypassed even if filters are defined.
+
+ Also preprocess the text, i.e. expand tabs and strip it if
+ wanted and applies registered filters.
+ """
+ if not isinstance(text, str):
+ if self.encoding == 'guess':
+ text, _ = guess_decode(text)
+ elif self.encoding == 'chardet':
+ try:
+ from pip._vendor import chardet
+ except ImportError as e:
+ raise ImportError('To enable chardet encoding guessing, '
+ 'please install the chardet library '
+ 'from http://chardet.feedparser.org/') from e
+ # check for BOM first
+ decoded = None
+ for bom, encoding in _encoding_map:
+ if text.startswith(bom):
+ decoded = text[len(bom):].decode(encoding, 'replace')
+ break
+ # no BOM found, so use chardet
+ if decoded is None:
+ enc = chardet.detect(text[:1024]) # Guess using first 1KB
+ decoded = text.decode(enc.get('encoding') or 'utf-8',
+ 'replace')
+ text = decoded
+ else:
+ text = text.decode(self.encoding)
+ if text.startswith('\ufeff'):
+ text = text[len('\ufeff'):]
+ else:
+ if text.startswith('\ufeff'):
+ text = text[len('\ufeff'):]
+
+ # text now *is* a unicode string
+ text = text.replace('\r\n', '\n')
+ text = text.replace('\r', '\n')
+ if self.stripall:
+ text = text.strip()
+ elif self.stripnl:
+ text = text.strip('\n')
+ if self.tabsize > 0:
+ text = text.expandtabs(self.tabsize)
+ if self.ensurenl and not text.endswith('\n'):
+ text += '\n'
+
+ def streamer():
+ for _, t, v in self.get_tokens_unprocessed(text):
+ yield t, v
+ stream = streamer()
+ if not unfiltered:
+ stream = apply_filters(stream, self.filters, self)
+ return stream
+
+ def get_tokens_unprocessed(self, text):
+ """
+ Return an iterable of (index, tokentype, value) pairs where "index"
+ is the starting position of the token within the input text.
+
+ In subclasses, implement this method as a generator to
+ maximize effectiveness.
+ """
+ raise NotImplementedError
+
+
+class DelegatingLexer(Lexer):
+ """
+ This lexer takes two lexer as arguments. A root lexer and
+ a language lexer. First everything is scanned using the language
+ lexer, afterwards all ``Other`` tokens are lexed using the root
+ lexer.
+
+ The lexers from the ``template`` lexer package use this base lexer.
+ """
+
+ def __init__(self, _root_lexer, _language_lexer, _needle=Other, **options):
+ self.root_lexer = _root_lexer(**options)
+ self.language_lexer = _language_lexer(**options)
+ self.needle = _needle
+ Lexer.__init__(self, **options)
+
+ def get_tokens_unprocessed(self, text):
+ buffered = ''
+ insertions = []
+ lng_buffer = []
+ for i, t, v in self.language_lexer.get_tokens_unprocessed(text):
+ if t is self.needle:
+ if lng_buffer:
+ insertions.append((len(buffered), lng_buffer))
+ lng_buffer = []
+ buffered += v
+ else:
+ lng_buffer.append((i, t, v))
+ if lng_buffer:
+ insertions.append((len(buffered), lng_buffer))
+ return do_insertions(insertions,
+ self.root_lexer.get_tokens_unprocessed(buffered))
+
+
+# ------------------------------------------------------------------------------
+# RegexLexer and ExtendedRegexLexer
+#
+
+
+class include(str): # pylint: disable=invalid-name
+ """
+ Indicates that a state should include rules from another state.
+ """
+ pass
+
+
+class _inherit:
+ """
+ Indicates the a state should inherit from its superclass.
+ """
+ def __repr__(self):
+ return 'inherit'
+
+inherit = _inherit() # pylint: disable=invalid-name
+
+
+class combined(tuple): # pylint: disable=invalid-name
+ """
+ Indicates a state combined from multiple states.
+ """
+
+ def __new__(cls, *args):
+ return tuple.__new__(cls, args)
+
+ def __init__(self, *args):
+ # tuple.__init__ doesn't do anything
+ pass
+
+
+class _PseudoMatch:
+ """
+ A pseudo match object constructed from a string.
+ """
+
+ def __init__(self, start, text):
+ self._text = text
+ self._start = start
+
+ def start(self, arg=None):
+ return self._start
+
+ def end(self, arg=None):
+ return self._start + len(self._text)
+
+ def group(self, arg=None):
+ if arg:
+ raise IndexError('No such group')
+ return self._text
+
+ def groups(self):
+ return (self._text,)
+
+ def groupdict(self):
+ return {}
+
+
+def bygroups(*args):
+ """
+ Callback that yields multiple actions for each group in the match.
+ """
+ def callback(lexer, match, ctx=None):
+ for i, action in enumerate(args):
+ if action is None:
+ continue
+ elif type(action) is _TokenType:
+ data = match.group(i + 1)
+ if data:
+ yield match.start(i + 1), action, data
+ else:
+ data = match.group(i + 1)
+ if data is not None:
+ if ctx:
+ ctx.pos = match.start(i + 1)
+ for item in action(lexer,
+ _PseudoMatch(match.start(i + 1), data), ctx):
+ if item:
+ yield item
+ if ctx:
+ ctx.pos = match.end()
+ return callback
+
+
+class _This:
+ """
+ Special singleton used for indicating the caller class.
+ Used by ``using``.
+ """
+
+this = _This()
+
+
+def using(_other, **kwargs):
+ """
+ Callback that processes the match with a different lexer.
+
+ The keyword arguments are forwarded to the lexer, except `state` which
+ is handled separately.
+
+ `state` specifies the state that the new lexer will start in, and can
+ be an enumerable such as ('root', 'inline', 'string') or a simple
+ string which is assumed to be on top of the root state.
+
+ Note: For that to work, `_other` must not be an `ExtendedRegexLexer`.
+ """
+ gt_kwargs = {}
+ if 'state' in kwargs:
+ s = kwargs.pop('state')
+ if isinstance(s, (list, tuple)):
+ gt_kwargs['stack'] = s
+ else:
+ gt_kwargs['stack'] = ('root', s)
+
+ if _other is this:
+ def callback(lexer, match, ctx=None):
+ # if keyword arguments are given the callback
+ # function has to create a new lexer instance
+ if kwargs:
+ # XXX: cache that somehow
+ kwargs.update(lexer.options)
+ lx = lexer.__class__(**kwargs)
+ else:
+ lx = lexer
+ s = match.start()
+ for i, t, v in lx.get_tokens_unprocessed(match.group(), **gt_kwargs):
+ yield i + s, t, v
+ if ctx:
+ ctx.pos = match.end()
+ else:
+ def callback(lexer, match, ctx=None):
+ # XXX: cache that somehow
+ kwargs.update(lexer.options)
+ lx = _other(**kwargs)
+
+ s = match.start()
+ for i, t, v in lx.get_tokens_unprocessed(match.group(), **gt_kwargs):
+ yield i + s, t, v
+ if ctx:
+ ctx.pos = match.end()
+ return callback
+
+
+class default:
+ """
+ Indicates a state or state action (e.g. #pop) to apply.
+ For example default('#pop') is equivalent to ('', Token, '#pop')
+ Note that state tuples may be used as well.
+
+ .. versionadded:: 2.0
+ """
+ def __init__(self, state):
+ self.state = state
+
+
+class words(Future):
+ """
+ Indicates a list of literal words that is transformed into an optimized
+ regex that matches any of the words.
+
+ .. versionadded:: 2.0
+ """
+ def __init__(self, words, prefix='', suffix=''):
+ self.words = words
+ self.prefix = prefix
+ self.suffix = suffix
+
+ def get(self):
+ return regex_opt(self.words, prefix=self.prefix, suffix=self.suffix)
+
+
+class RegexLexerMeta(LexerMeta):
+ """
+ Metaclass for RegexLexer, creates the self._tokens attribute from
+ self.tokens on the first instantiation.
+ """
+
+ def _process_regex(cls, regex, rflags, state):
+ """Preprocess the regular expression component of a token definition."""
+ if isinstance(regex, Future):
+ regex = regex.get()
+ return re.compile(regex, rflags).match
+
+ def _process_token(cls, token):
+ """Preprocess the token component of a token definition."""
+ assert type(token) is _TokenType or callable(token), \
+ 'token type must be simple type or callable, not %r' % (token,)
+ return token
+
+ def _process_new_state(cls, new_state, unprocessed, processed):
+ """Preprocess the state transition action of a token definition."""
+ if isinstance(new_state, str):
+ # an existing state
+ if new_state == '#pop':
+ return -1
+ elif new_state in unprocessed:
+ return (new_state,)
+ elif new_state == '#push':
+ return new_state
+ elif new_state[:5] == '#pop:':
+ return -int(new_state[5:])
+ else:
+ assert False, 'unknown new state %r' % new_state
+ elif isinstance(new_state, combined):
+ # combine a new state from existing ones
+ tmp_state = '_tmp_%d' % cls._tmpname
+ cls._tmpname += 1
+ itokens = []
+ for istate in new_state:
+ assert istate != new_state, 'circular state ref %r' % istate
+ itokens.extend(cls._process_state(unprocessed,
+ processed, istate))
+ processed[tmp_state] = itokens
+ return (tmp_state,)
+ elif isinstance(new_state, tuple):
+ # push more than one state
+ for istate in new_state:
+ assert (istate in unprocessed or
+ istate in ('#pop', '#push')), \
+ 'unknown new state ' + istate
+ return new_state
+ else:
+ assert False, 'unknown new state def %r' % new_state
+
+ def _process_state(cls, unprocessed, processed, state):
+ """Preprocess a single state definition."""
+ assert type(state) is str, "wrong state name %r" % state
+ assert state[0] != '#', "invalid state name %r" % state
+ if state in processed:
+ return processed[state]
+ tokens = processed[state] = []
+ rflags = cls.flags
+ for tdef in unprocessed[state]:
+ if isinstance(tdef, include):
+ # it's a state reference
+ assert tdef != state, "circular state reference %r" % state
+ tokens.extend(cls._process_state(unprocessed, processed,
+ str(tdef)))
+ continue
+ if isinstance(tdef, _inherit):
+ # should be processed already, but may not in the case of:
+ # 1. the state has no counterpart in any parent
+ # 2. the state includes more than one 'inherit'
+ continue
+ if isinstance(tdef, default):
+ new_state = cls._process_new_state(tdef.state, unprocessed, processed)
+ tokens.append((re.compile('').match, None, new_state))
+ continue
+
+ assert type(tdef) is tuple, "wrong rule def %r" % tdef
+
+ try:
+ rex = cls._process_regex(tdef[0], rflags, state)
+ except Exception as err:
+ raise ValueError("uncompilable regex %r in state %r of %r: %s" %
+ (tdef[0], state, cls, err)) from err
+
+ token = cls._process_token(tdef[1])
+
+ if len(tdef) == 2:
+ new_state = None
+ else:
+ new_state = cls._process_new_state(tdef[2],
+ unprocessed, processed)
+
+ tokens.append((rex, token, new_state))
+ return tokens
+
+ def process_tokendef(cls, name, tokendefs=None):
+ """Preprocess a dictionary of token definitions."""
+ processed = cls._all_tokens[name] = {}
+ tokendefs = tokendefs or cls.tokens[name]
+ for state in list(tokendefs):
+ cls._process_state(tokendefs, processed, state)
+ return processed
+
+ def get_tokendefs(cls):
+ """
+ Merge tokens from superclasses in MRO order, returning a single tokendef
+ dictionary.
+
+ Any state that is not defined by a subclass will be inherited
+ automatically. States that *are* defined by subclasses will, by
+ default, override that state in the superclass. If a subclass wishes to
+ inherit definitions from a superclass, it can use the special value
+ "inherit", which will cause the superclass' state definition to be
+ included at that point in the state.
+ """
+ tokens = {}
+ inheritable = {}
+ for c in cls.__mro__:
+ toks = c.__dict__.get('tokens', {})
+
+ for state, items in toks.items():
+ curitems = tokens.get(state)
+ if curitems is None:
+ # N.b. because this is assigned by reference, sufficiently
+ # deep hierarchies are processed incrementally (e.g. for
+ # A(B), B(C), C(RegexLexer), B will be premodified so X(B)
+ # will not see any inherits in B).
+ tokens[state] = items
+ try:
+ inherit_ndx = items.index(inherit)
+ except ValueError:
+ continue
+ inheritable[state] = inherit_ndx
+ continue
+
+ inherit_ndx = inheritable.pop(state, None)
+ if inherit_ndx is None:
+ continue
+
+ # Replace the "inherit" value with the items
+ curitems[inherit_ndx:inherit_ndx+1] = items
+ try:
+ # N.b. this is the index in items (that is, the superclass
+ # copy), so offset required when storing below.
+ new_inh_ndx = items.index(inherit)
+ except ValueError:
+ pass
+ else:
+ inheritable[state] = inherit_ndx + new_inh_ndx
+
+ return tokens
+
+ def __call__(cls, *args, **kwds):
+ """Instantiate cls after preprocessing its token definitions."""
+ if '_tokens' not in cls.__dict__:
+ cls._all_tokens = {}
+ cls._tmpname = 0
+ if hasattr(cls, 'token_variants') and cls.token_variants:
+ # don't process yet
+ pass
+ else:
+ cls._tokens = cls.process_tokendef('', cls.get_tokendefs())
+
+ return type.__call__(cls, *args, **kwds)
+
+
+class RegexLexer(Lexer, metaclass=RegexLexerMeta):
+ """
+ Base for simple stateful regular expression-based lexers.
+ Simplifies the lexing process so that you need only
+ provide a list of states and regular expressions.
+ """
+
+ #: Flags for compiling the regular expressions.
+ #: Defaults to MULTILINE.
+ flags = re.MULTILINE
+
+ #: At all time there is a stack of states. Initially, the stack contains
+ #: a single state 'root'. The top of the stack is called "the current state".
+ #:
+ #: Dict of ``{'state': [(regex, tokentype, new_state), ...], ...}``
+ #:
+ #: ``new_state`` can be omitted to signify no state transition.
+ #: If ``new_state`` is a string, it is pushed on the stack. This ensure
+ #: the new current state is ``new_state``.
+ #: If ``new_state`` is a tuple of strings, all of those strings are pushed
+ #: on the stack and the current state will be the last element of the list.
+ #: ``new_state`` can also be ``combined('state1', 'state2', ...)``
+ #: to signify a new, anonymous state combined from the rules of two
+ #: or more existing ones.
+ #: Furthermore, it can be '#pop' to signify going back one step in
+ #: the state stack, or '#push' to push the current state on the stack
+ #: again. Note that if you push while in a combined state, the combined
+ #: state itself is pushed, and not only the state in which the rule is
+ #: defined.
+ #:
+ #: The tuple can also be replaced with ``include('state')``, in which
+ #: case the rules from the state named by the string are included in the
+ #: current one.
+ tokens = {}
+
+ def get_tokens_unprocessed(self, text, stack=('root',)):
+ """
+ Split ``text`` into (tokentype, text) pairs.
+
+ ``stack`` is the initial stack (default: ``['root']``)
+ """
+ pos = 0
+ tokendefs = self._tokens
+ statestack = list(stack)
+ statetokens = tokendefs[statestack[-1]]
+ while 1:
+ for rexmatch, action, new_state in statetokens:
+ m = rexmatch(text, pos)
+ if m:
+ if action is not None:
+ if type(action) is _TokenType:
+ yield pos, action, m.group()
+ else:
+ yield from action(self, m)
+ pos = m.end()
+ if new_state is not None:
+ # state transition
+ if isinstance(new_state, tuple):
+ for state in new_state:
+ if state == '#pop':
+ if len(statestack) > 1:
+ statestack.pop()
+ elif state == '#push':
+ statestack.append(statestack[-1])
+ else:
+ statestack.append(state)
+ elif isinstance(new_state, int):
+ # pop, but keep at least one state on the stack
+ # (random code leading to unexpected pops should
+ # not allow exceptions)
+ if abs(new_state) >= len(statestack):
+ del statestack[1:]
+ else:
+ del statestack[new_state:]
+ elif new_state == '#push':
+ statestack.append(statestack[-1])
+ else:
+ assert False, "wrong state def: %r" % new_state
+ statetokens = tokendefs[statestack[-1]]
+ break
+ else:
+ # We are here only if all state tokens have been considered
+ # and there was not a match on any of them.
+ try:
+ if text[pos] == '\n':
+ # at EOL, reset state to "root"
+ statestack = ['root']
+ statetokens = tokendefs['root']
+ yield pos, Text, '\n'
+ pos += 1
+ continue
+ yield pos, Error, text[pos]
+ pos += 1
+ except IndexError:
+ break
+
+
+class LexerContext:
+ """
+ A helper object that holds lexer position data.
+ """
+
+ def __init__(self, text, pos, stack=None, end=None):
+ self.text = text
+ self.pos = pos
+ self.end = end or len(text) # end=0 not supported ;-)
+ self.stack = stack or ['root']
+
+ def __repr__(self):
+ return 'LexerContext(%r, %r, %r)' % (
+ self.text, self.pos, self.stack)
+
+
+class ExtendedRegexLexer(RegexLexer):
+ """
+ A RegexLexer that uses a context object to store its state.
+ """
+
+ def get_tokens_unprocessed(self, text=None, context=None):
+ """
+ Split ``text`` into (tokentype, text) pairs.
+ If ``context`` is given, use this lexer context instead.
+ """
+ tokendefs = self._tokens
+ if not context:
+ ctx = LexerContext(text, 0)
+ statetokens = tokendefs['root']
+ else:
+ ctx = context
+ statetokens = tokendefs[ctx.stack[-1]]
+ text = ctx.text
+ while 1:
+ for rexmatch, action, new_state in statetokens:
+ m = rexmatch(text, ctx.pos, ctx.end)
+ if m:
+ if action is not None:
+ if type(action) is _TokenType:
+ yield ctx.pos, action, m.group()
+ ctx.pos = m.end()
+ else:
+ yield from action(self, m, ctx)
+ if not new_state:
+ # altered the state stack?
+ statetokens = tokendefs[ctx.stack[-1]]
+ # CAUTION: callback must set ctx.pos!
+ if new_state is not None:
+ # state transition
+ if isinstance(new_state, tuple):
+ for state in new_state:
+ if state == '#pop':
+ if len(ctx.stack) > 1:
+ ctx.stack.pop()
+ elif state == '#push':
+ ctx.stack.append(ctx.stack[-1])
+ else:
+ ctx.stack.append(state)
+ elif isinstance(new_state, int):
+ # see RegexLexer for why this check is made
+ if abs(new_state) >= len(ctx.stack):
+ del ctx.stack[1:]
+ else:
+ del ctx.stack[new_state:]
+ elif new_state == '#push':
+ ctx.stack.append(ctx.stack[-1])
+ else:
+ assert False, "wrong state def: %r" % new_state
+ statetokens = tokendefs[ctx.stack[-1]]
+ break
+ else:
+ try:
+ if ctx.pos >= ctx.end:
+ break
+ if text[ctx.pos] == '\n':
+ # at EOL, reset state to "root"
+ ctx.stack = ['root']
+ statetokens = tokendefs['root']
+ yield ctx.pos, Text, '\n'
+ ctx.pos += 1
+ continue
+ yield ctx.pos, Error, text[ctx.pos]
+ ctx.pos += 1
+ except IndexError:
+ break
+
+
+def do_insertions(insertions, tokens):
+ """
+ Helper for lexers which must combine the results of several
+ sublexers.
+
+ ``insertions`` is a list of ``(index, itokens)`` pairs.
+ Each ``itokens`` iterable should be inserted at position
+ ``index`` into the token stream given by the ``tokens``
+ argument.
+
+ The result is a combined token stream.
+
+ TODO: clean up the code here.
+ """
+ insertions = iter(insertions)
+ try:
+ index, itokens = next(insertions)
+ except StopIteration:
+ # no insertions
+ yield from tokens
+ return
+
+ realpos = None
+ insleft = True
+
+ # iterate over the token stream where we want to insert
+ # the tokens from the insertion list.
+ for i, t, v in tokens:
+ # first iteration. store the position of first item
+ if realpos is None:
+ realpos = i
+ oldi = 0
+ while insleft and i + len(v) >= index:
+ tmpval = v[oldi:index - i]
+ if tmpval:
+ yield realpos, t, tmpval
+ realpos += len(tmpval)
+ for it_index, it_token, it_value in itokens:
+ yield realpos, it_token, it_value
+ realpos += len(it_value)
+ oldi = index - i
+ try:
+ index, itokens = next(insertions)
+ except StopIteration:
+ insleft = False
+ break # not strictly necessary
+ if oldi < len(v):
+ yield realpos, t, v[oldi:]
+ realpos += len(v) - oldi
+
+ # leftover tokens
+ while insleft:
+ # no normal tokens, set realpos to zero
+ realpos = realpos or 0
+ for p, t, v in itokens:
+ yield realpos, t, v
+ realpos += len(v)
+ try:
+ index, itokens = next(insertions)
+ except StopIteration:
+ insleft = False
+ break # not strictly necessary
+
+
+class ProfilingRegexLexerMeta(RegexLexerMeta):
+ """Metaclass for ProfilingRegexLexer, collects regex timing info."""
+
+ def _process_regex(cls, regex, rflags, state):
+ if isinstance(regex, words):
+ rex = regex_opt(regex.words, prefix=regex.prefix,
+ suffix=regex.suffix)
+ else:
+ rex = regex
+ compiled = re.compile(rex, rflags)
+
+ def match_func(text, pos, endpos=sys.maxsize):
+ info = cls._prof_data[-1].setdefault((state, rex), [0, 0.0])
+ t0 = time.time()
+ res = compiled.match(text, pos, endpos)
+ t1 = time.time()
+ info[0] += 1
+ info[1] += t1 - t0
+ return res
+ return match_func
+
+
+class ProfilingRegexLexer(RegexLexer, metaclass=ProfilingRegexLexerMeta):
+ """Drop-in replacement for RegexLexer that does profiling of its regexes."""
+
+ _prof_data = []
+ _prof_sort_index = 4 # defaults to time per call
+
+ def get_tokens_unprocessed(self, text, stack=('root',)):
+ # this needs to be a stack, since using(this) will produce nested calls
+ self.__class__._prof_data.append({})
+ yield from RegexLexer.get_tokens_unprocessed(self, text, stack)
+ rawdata = self.__class__._prof_data.pop()
+ data = sorted(((s, repr(r).strip('u\'').replace('\\\\', '\\')[:65],
+ n, 1000 * t, 1000 * t / n)
+ for ((s, r), (n, t)) in rawdata.items()),
+ key=lambda x: x[self._prof_sort_index],
+ reverse=True)
+ sum_total = sum(x[3] for x in data)
+
+ print()
+ print('Profiling result for %s lexing %d chars in %.3f ms' %
+ (self.__class__.__name__, len(text), sum_total))
+ print('=' * 110)
+ print('%-20s %-64s ncalls tottime percall' % ('state', 'regex'))
+ print('-' * 110)
+ for d in data:
+ print('%-20s %-65s %5d %8.4f %8.4f' % d)
+ print('=' * 110)
diff --git a/src/pip/_vendor/pygments/lexers/__init__.py b/src/pip/_vendor/pygments/lexers/__init__.py
new file mode 100644
index 000000000..ed69f24ed
--- /dev/null
+++ b/src/pip/_vendor/pygments/lexers/__init__.py
@@ -0,0 +1,335 @@
+"""
+ pygments.lexers
+ ~~~~~~~~~~~~~~~
+
+ Pygments lexers.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import re
+import sys
+import types
+from fnmatch import fnmatch
+from os.path import basename
+
+from pip._vendor.pygments.lexers._mapping import LEXERS
+from pip._vendor.pygments.modeline import get_filetype_from_buffer
+from pip._vendor.pygments.plugin import find_plugin_lexers
+from pip._vendor.pygments.util import ClassNotFound, guess_decode
+
+COMPAT = {
+ 'Python3Lexer': 'PythonLexer',
+ 'Python3TracebackLexer': 'PythonTracebackLexer',
+}
+
+__all__ = ['get_lexer_by_name', 'get_lexer_for_filename', 'find_lexer_class',
+ 'guess_lexer', 'load_lexer_from_file'] + list(LEXERS) + list(COMPAT)
+
+_lexer_cache = {}
+
+def _load_lexers(module_name):
+ """Load a lexer (and all others in the module too)."""
+ mod = __import__(module_name, None, None, ['__all__'])
+ for lexer_name in mod.__all__:
+ cls = getattr(mod, lexer_name)
+ _lexer_cache[cls.name] = cls
+
+
+def get_all_lexers(plugins=True):
+ """Return a generator of tuples in the form ``(name, aliases,
+ filenames, mimetypes)`` of all know lexers.
+
+ If *plugins* is true (the default), plugin lexers supplied by entrypoints
+ are also returned. Otherwise, only builtin ones are considered.
+ """
+ for item in LEXERS.values():
+ yield item[1:]
+ if plugins:
+ for lexer in find_plugin_lexers():
+ yield lexer.name, lexer.aliases, lexer.filenames, lexer.mimetypes
+
+
+def find_lexer_class(name):
+ """Lookup a lexer class by name.
+
+ Return None if not found.
+ """
+ if name in _lexer_cache:
+ return _lexer_cache[name]
+ # lookup builtin lexers
+ for module_name, lname, aliases, _, _ in LEXERS.values():
+ if name == lname:
+ _load_lexers(module_name)
+ return _lexer_cache[name]
+ # continue with lexers from setuptools entrypoints
+ for cls in find_plugin_lexers():
+ if cls.name == name:
+ return cls
+
+
+def find_lexer_class_by_name(_alias):
+ """Lookup a lexer class by alias.
+
+ Like `get_lexer_by_name`, but does not instantiate the class.
+
+ .. versionadded:: 2.2
+ """
+ if not _alias:
+ raise ClassNotFound('no lexer for alias %r found' % _alias)
+ # lookup builtin lexers
+ for module_name, name, aliases, _, _ in LEXERS.values():
+ if _alias.lower() in aliases:
+ if name not in _lexer_cache:
+ _load_lexers(module_name)
+ return _lexer_cache[name]
+ # continue with lexers from setuptools entrypoints
+ for cls in find_plugin_lexers():
+ if _alias.lower() in cls.aliases:
+ return cls
+ raise ClassNotFound('no lexer for alias %r found' % _alias)
+
+
+def get_lexer_by_name(_alias, **options):
+ """Get a lexer by an alias.
+
+ Raises ClassNotFound if not found.
+ """
+ if not _alias:
+ raise ClassNotFound('no lexer for alias %r found' % _alias)
+
+ # lookup builtin lexers
+ for module_name, name, aliases, _, _ in LEXERS.values():
+ if _alias.lower() in aliases:
+ if name not in _lexer_cache:
+ _load_lexers(module_name)
+ return _lexer_cache[name](**options)
+ # continue with lexers from setuptools entrypoints
+ for cls in find_plugin_lexers():
+ if _alias.lower() in cls.aliases:
+ return cls(**options)
+ raise ClassNotFound('no lexer for alias %r found' % _alias)
+
+
+def load_lexer_from_file(filename, lexername="CustomLexer", **options):
+ """Load a lexer from a file.
+
+ This method expects a file located relative to the current working
+ directory, which contains a Lexer class. By default, it expects the
+ Lexer to be name CustomLexer; you can specify your own class name
+ as the second argument to this function.
+
+ Users should be very careful with the input, because this method
+ is equivalent to running eval on the input file.
+
+ Raises ClassNotFound if there are any problems importing the Lexer.
+
+ .. versionadded:: 2.2
+ """
+ try:
+ # This empty dict will contain the namespace for the exec'd file
+ custom_namespace = {}
+ with open(filename, 'rb') as f:
+ exec(f.read(), custom_namespace)
+ # Retrieve the class `lexername` from that namespace
+ if lexername not in custom_namespace:
+ raise ClassNotFound('no valid %s class found in %s' %
+ (lexername, filename))
+ lexer_class = custom_namespace[lexername]
+ # And finally instantiate it with the options
+ return lexer_class(**options)
+ except OSError as err:
+ raise ClassNotFound('cannot read %s: %s' % (filename, err))
+ except ClassNotFound:
+ raise
+ except Exception as err:
+ raise ClassNotFound('error when loading custom lexer: %s' % err)
+
+
+def find_lexer_class_for_filename(_fn, code=None):
+ """Get a lexer for a filename.
+
+ If multiple lexers match the filename pattern, use ``analyse_text()`` to
+ figure out which one is more appropriate.
+
+ Returns None if not found.
+ """
+ matches = []
+ fn = basename(_fn)
+ for modname, name, _, filenames, _ in LEXERS.values():
+ for filename in filenames:
+ if fnmatch(fn, filename):
+ if name not in _lexer_cache:
+ _load_lexers(modname)
+ matches.append((_lexer_cache[name], filename))
+ for cls in find_plugin_lexers():
+ for filename in cls.filenames:
+ if fnmatch(fn, filename):
+ matches.append((cls, filename))
+
+ if isinstance(code, bytes):
+ # decode it, since all analyse_text functions expect unicode
+ code = guess_decode(code)
+
+ def get_rating(info):
+ cls, filename = info
+ # explicit patterns get a bonus
+ bonus = '*' not in filename and 0.5 or 0
+ # The class _always_ defines analyse_text because it's included in
+ # the Lexer class. The default implementation returns None which
+ # gets turned into 0.0. Run scripts/detect_missing_analyse_text.py
+ # to find lexers which need it overridden.
+ if code:
+ return cls.analyse_text(code) + bonus, cls.__name__
+ return cls.priority + bonus, cls.__name__
+
+ if matches:
+ matches.sort(key=get_rating)
+ # print "Possible lexers, after sort:", matches
+ return matches[-1][0]
+
+
+def get_lexer_for_filename(_fn, code=None, **options):
+ """Get a lexer for a filename.
+
+ If multiple lexers match the filename pattern, use ``analyse_text()`` to
+ figure out which one is more appropriate.
+
+ Raises ClassNotFound if not found.
+ """
+ res = find_lexer_class_for_filename(_fn, code)
+ if not res:
+ raise ClassNotFound('no lexer for filename %r found' % _fn)
+ return res(**options)
+
+
+def get_lexer_for_mimetype(_mime, **options):
+ """Get a lexer for a mimetype.
+
+ Raises ClassNotFound if not found.
+ """
+ for modname, name, _, _, mimetypes in LEXERS.values():
+ if _mime in mimetypes:
+ if name not in _lexer_cache:
+ _load_lexers(modname)
+ return _lexer_cache[name](**options)
+ for cls in find_plugin_lexers():
+ if _mime in cls.mimetypes:
+ return cls(**options)
+ raise ClassNotFound('no lexer for mimetype %r found' % _mime)
+
+
+def _iter_lexerclasses(plugins=True):
+ """Return an iterator over all lexer classes."""
+ for key in sorted(LEXERS):
+ module_name, name = LEXERS[key][:2]
+ if name not in _lexer_cache:
+ _load_lexers(module_name)
+ yield _lexer_cache[name]
+ if plugins:
+ yield from find_plugin_lexers()
+
+
+def guess_lexer_for_filename(_fn, _text, **options):
+ """
+ Lookup all lexers that handle those filenames primary (``filenames``)
+ or secondary (``alias_filenames``). Then run a text analysis for those
+ lexers and choose the best result.
+
+ usage::
+
+ >>> from pygments.lexers import guess_lexer_for_filename
+ >>> guess_lexer_for_filename('hello.html', '<%= @foo %>')
+ <pygments.lexers.templates.RhtmlLexer object at 0xb7d2f32c>
+ >>> guess_lexer_for_filename('hello.html', '<h1>{{ title|e }}</h1>')
+ <pygments.lexers.templates.HtmlDjangoLexer object at 0xb7d2f2ac>
+ >>> guess_lexer_for_filename('style.css', 'a { color: <?= $link ?> }')
+ <pygments.lexers.templates.CssPhpLexer object at 0xb7ba518c>
+ """
+ fn = basename(_fn)
+ primary = {}
+ matching_lexers = set()
+ for lexer in _iter_lexerclasses():
+ for filename in lexer.filenames:
+ if fnmatch(fn, filename):
+ matching_lexers.add(lexer)
+ primary[lexer] = True
+ for filename in lexer.alias_filenames:
+ if fnmatch(fn, filename):
+ matching_lexers.add(lexer)
+ primary[lexer] = False
+ if not matching_lexers:
+ raise ClassNotFound('no lexer for filename %r found' % fn)
+ if len(matching_lexers) == 1:
+ return matching_lexers.pop()(**options)
+ result = []
+ for lexer in matching_lexers:
+ rv = lexer.analyse_text(_text)
+ if rv == 1.0:
+ return lexer(**options)
+ result.append((rv, lexer))
+
+ def type_sort(t):
+ # sort by:
+ # - analyse score
+ # - is primary filename pattern?
+ # - priority
+ # - last resort: class name
+ return (t[0], primary[t[1]], t[1].priority, t[1].__name__)
+ result.sort(key=type_sort)
+
+ return result[-1][1](**options)
+
+
+def guess_lexer(_text, **options):
+ """Guess a lexer by strong distinctions in the text (eg, shebang)."""
+
+ if not isinstance(_text, str):
+ inencoding = options.get('inencoding', options.get('encoding'))
+ if inencoding:
+ _text = _text.decode(inencoding or 'utf8')
+ else:
+ _text, _ = guess_decode(_text)
+
+ # try to get a vim modeline first
+ ft = get_filetype_from_buffer(_text)
+
+ if ft is not None:
+ try:
+ return get_lexer_by_name(ft, **options)
+ except ClassNotFound:
+ pass
+
+ best_lexer = [0.0, None]
+ for lexer in _iter_lexerclasses():
+ rv = lexer.analyse_text(_text)
+ if rv == 1.0:
+ return lexer(**options)
+ if rv > best_lexer[0]:
+ best_lexer[:] = (rv, lexer)
+ if not best_lexer[0] or best_lexer[1] is None:
+ raise ClassNotFound('no lexer matching the text found')
+ return best_lexer[1](**options)
+
+
+class _automodule(types.ModuleType):
+ """Automatically import lexers."""
+
+ def __getattr__(self, name):
+ info = LEXERS.get(name)
+ if info:
+ _load_lexers(info[0])
+ cls = _lexer_cache[info[1]]
+ setattr(self, name, cls)
+ return cls
+ if name in COMPAT:
+ return getattr(self, COMPAT[name])
+ raise AttributeError(name)
+
+
+oldmod = sys.modules[__name__]
+newmod = _automodule(__name__)
+newmod.__dict__.update(oldmod.__dict__)
+sys.modules[__name__] = newmod
+del newmod.newmod, newmod.oldmod, newmod.sys, newmod.types
diff --git a/src/pip/_vendor/pygments/lexers/_mapping.py b/src/pip/_vendor/pygments/lexers/_mapping.py
new file mode 100644
index 000000000..40dcaa3c7
--- /dev/null
+++ b/src/pip/_vendor/pygments/lexers/_mapping.py
@@ -0,0 +1,541 @@
+# Automatically generated by scripts/gen_mapfiles.py.
+# DO NOT EDIT BY HAND; run `make mapfiles` instead.
+
+LEXERS = {
+ 'ABAPLexer': ('pip._vendor.pygments.lexers.business', 'ABAP', ('abap',), ('*.abap', '*.ABAP'), ('text/x-abap',)),
+ 'AMDGPULexer': ('pip._vendor.pygments.lexers.amdgpu', 'AMDGPU', ('amdgpu',), ('*.isa',), ()),
+ 'APLLexer': ('pip._vendor.pygments.lexers.apl', 'APL', ('apl',), ('*.apl', '*.aplf', '*.aplo', '*.apln', '*.aplc', '*.apli', '*.dyalog'), ()),
+ 'AbnfLexer': ('pip._vendor.pygments.lexers.grammar_notation', 'ABNF', ('abnf',), ('*.abnf',), ('text/x-abnf',)),
+ 'ActionScript3Lexer': ('pip._vendor.pygments.lexers.actionscript', 'ActionScript 3', ('actionscript3', 'as3'), ('*.as',), ('application/x-actionscript3', 'text/x-actionscript3', 'text/actionscript3')),
+ 'ActionScriptLexer': ('pip._vendor.pygments.lexers.actionscript', 'ActionScript', ('actionscript', 'as'), ('*.as',), ('application/x-actionscript', 'text/x-actionscript', 'text/actionscript')),
+ 'AdaLexer': ('pip._vendor.pygments.lexers.ada', 'Ada', ('ada', 'ada95', 'ada2005'), ('*.adb', '*.ads', '*.ada'), ('text/x-ada',)),
+ 'AdlLexer': ('pip._vendor.pygments.lexers.archetype', 'ADL', ('adl',), ('*.adl', '*.adls', '*.adlf', '*.adlx'), ()),
+ 'AgdaLexer': ('pip._vendor.pygments.lexers.haskell', 'Agda', ('agda',), ('*.agda',), ('text/x-agda',)),
+ 'AheuiLexer': ('pip._vendor.pygments.lexers.esoteric', 'Aheui', ('aheui',), ('*.aheui',), ()),
+ 'AlloyLexer': ('pip._vendor.pygments.lexers.dsls', 'Alloy', ('alloy',), ('*.als',), ('text/x-alloy',)),
+ 'AmbientTalkLexer': ('pip._vendor.pygments.lexers.ambient', 'AmbientTalk', ('ambienttalk', 'ambienttalk/2', 'at'), ('*.at',), ('text/x-ambienttalk',)),
+ 'AmplLexer': ('pip._vendor.pygments.lexers.ampl', 'Ampl', ('ampl',), ('*.run',), ()),
+ 'Angular2HtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML + Angular2', ('html+ng2',), ('*.ng2',), ()),
+ 'Angular2Lexer': ('pip._vendor.pygments.lexers.templates', 'Angular2', ('ng2',), (), ()),
+ 'AntlrActionScriptLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With ActionScript Target', ('antlr-actionscript', 'antlr-as'), ('*.G', '*.g'), ()),
+ 'AntlrCSharpLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With C# Target', ('antlr-csharp', 'antlr-c#'), ('*.G', '*.g'), ()),
+ 'AntlrCppLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With CPP Target', ('antlr-cpp',), ('*.G', '*.g'), ()),
+ 'AntlrJavaLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With Java Target', ('antlr-java',), ('*.G', '*.g'), ()),
+ 'AntlrLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR', ('antlr',), (), ()),
+ 'AntlrObjectiveCLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With ObjectiveC Target', ('antlr-objc',), ('*.G', '*.g'), ()),
+ 'AntlrPerlLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With Perl Target', ('antlr-perl',), ('*.G', '*.g'), ()),
+ 'AntlrPythonLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With Python Target', ('antlr-python',), ('*.G', '*.g'), ()),
+ 'AntlrRubyLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With Ruby Target', ('antlr-ruby', 'antlr-rb'), ('*.G', '*.g'), ()),
+ 'ApacheConfLexer': ('pip._vendor.pygments.lexers.configs', 'ApacheConf', ('apacheconf', 'aconf', 'apache'), ('.htaccess', 'apache.conf', 'apache2.conf'), ('text/x-apacheconf',)),
+ 'AppleScriptLexer': ('pip._vendor.pygments.lexers.scripting', 'AppleScript', ('applescript',), ('*.applescript',), ()),
+ 'ArduinoLexer': ('pip._vendor.pygments.lexers.c_like', 'Arduino', ('arduino',), ('*.ino',), ('text/x-arduino',)),
+ 'ArrowLexer': ('pip._vendor.pygments.lexers.arrow', 'Arrow', ('arrow',), ('*.arw',), ()),
+ 'AscLexer': ('pip._vendor.pygments.lexers.asc', 'ASCII armored', ('asc', 'pem'), ('*.asc', '*.pem', 'id_dsa', 'id_ecdsa', 'id_ecdsa_sk', 'id_ed25519', 'id_ed25519_sk', 'id_rsa'), ('application/pgp-keys', 'application/pgp-encrypted', 'application/pgp-signature')),
+ 'AspectJLexer': ('pip._vendor.pygments.lexers.jvm', 'AspectJ', ('aspectj',), ('*.aj',), ('text/x-aspectj',)),
+ 'AsymptoteLexer': ('pip._vendor.pygments.lexers.graphics', 'Asymptote', ('asymptote', 'asy'), ('*.asy',), ('text/x-asymptote',)),
+ 'AugeasLexer': ('pip._vendor.pygments.lexers.configs', 'Augeas', ('augeas',), ('*.aug',), ()),
+ 'AutoItLexer': ('pip._vendor.pygments.lexers.automation', 'AutoIt', ('autoit',), ('*.au3',), ('text/x-autoit',)),
+ 'AutohotkeyLexer': ('pip._vendor.pygments.lexers.automation', 'autohotkey', ('autohotkey', 'ahk'), ('*.ahk', '*.ahkl'), ('text/x-autohotkey',)),
+ 'AwkLexer': ('pip._vendor.pygments.lexers.textedit', 'Awk', ('awk', 'gawk', 'mawk', 'nawk'), ('*.awk',), ('application/x-awk',)),
+ 'BBCBasicLexer': ('pip._vendor.pygments.lexers.basic', 'BBC Basic', ('bbcbasic',), ('*.bbc',), ()),
+ 'BBCodeLexer': ('pip._vendor.pygments.lexers.markup', 'BBCode', ('bbcode',), (), ('text/x-bbcode',)),
+ 'BCLexer': ('pip._vendor.pygments.lexers.algebra', 'BC', ('bc',), ('*.bc',), ()),
+ 'BSTLexer': ('pip._vendor.pygments.lexers.bibtex', 'BST', ('bst', 'bst-pybtex'), ('*.bst',), ()),
+ 'BareLexer': ('pip._vendor.pygments.lexers.bare', 'BARE', ('bare',), ('*.bare',), ()),
+ 'BaseMakefileLexer': ('pip._vendor.pygments.lexers.make', 'Base Makefile', ('basemake',), (), ()),
+ 'BashLexer': ('pip._vendor.pygments.lexers.shell', 'Bash', ('bash', 'sh', 'ksh', 'zsh', 'shell'), ('*.sh', '*.ksh', '*.bash', '*.ebuild', '*.eclass', '*.exheres-0', '*.exlib', '*.zsh', '.bashrc', 'bashrc', '.bash_*', 'bash_*', 'zshrc', '.zshrc', '.kshrc', 'kshrc', 'PKGBUILD'), ('application/x-sh', 'application/x-shellscript', 'text/x-shellscript')),
+ 'BashSessionLexer': ('pip._vendor.pygments.lexers.shell', 'Bash Session', ('console', 'shell-session'), ('*.sh-session', '*.shell-session'), ('application/x-shell-session', 'application/x-sh-session')),
+ 'BatchLexer': ('pip._vendor.pygments.lexers.shell', 'Batchfile', ('batch', 'bat', 'dosbatch', 'winbatch'), ('*.bat', '*.cmd'), ('application/x-dos-batch',)),
+ 'BddLexer': ('pip._vendor.pygments.lexers.bdd', 'Bdd', ('bdd',), ('*.feature',), ('text/x-bdd',)),
+ 'BefungeLexer': ('pip._vendor.pygments.lexers.esoteric', 'Befunge', ('befunge',), ('*.befunge',), ('application/x-befunge',)),
+ 'BerryLexer': ('pip._vendor.pygments.lexers.berry', 'Berry', ('berry', 'be'), ('*.be',), ('text/x-berry', 'application/x-berry')),
+ 'BibTeXLexer': ('pip._vendor.pygments.lexers.bibtex', 'BibTeX', ('bibtex', 'bib'), ('*.bib',), ('text/x-bibtex',)),
+ 'BlitzBasicLexer': ('pip._vendor.pygments.lexers.basic', 'BlitzBasic', ('blitzbasic', 'b3d', 'bplus'), ('*.bb', '*.decls'), ('text/x-bb',)),
+ 'BlitzMaxLexer': ('pip._vendor.pygments.lexers.basic', 'BlitzMax', ('blitzmax', 'bmax'), ('*.bmx',), ('text/x-bmx',)),
+ 'BnfLexer': ('pip._vendor.pygments.lexers.grammar_notation', 'BNF', ('bnf',), ('*.bnf',), ('text/x-bnf',)),
+ 'BoaLexer': ('pip._vendor.pygments.lexers.boa', 'Boa', ('boa',), ('*.boa',), ()),
+ 'BooLexer': ('pip._vendor.pygments.lexers.dotnet', 'Boo', ('boo',), ('*.boo',), ('text/x-boo',)),
+ 'BoogieLexer': ('pip._vendor.pygments.lexers.verification', 'Boogie', ('boogie',), ('*.bpl',), ()),
+ 'BrainfuckLexer': ('pip._vendor.pygments.lexers.esoteric', 'Brainfuck', ('brainfuck', 'bf'), ('*.bf', '*.b'), ('application/x-brainfuck',)),
+ 'BugsLexer': ('pip._vendor.pygments.lexers.modeling', 'BUGS', ('bugs', 'winbugs', 'openbugs'), ('*.bug',), ()),
+ 'CAmkESLexer': ('pip._vendor.pygments.lexers.esoteric', 'CAmkES', ('camkes', 'idl4'), ('*.camkes', '*.idl4'), ()),
+ 'CLexer': ('pip._vendor.pygments.lexers.c_cpp', 'C', ('c',), ('*.c', '*.h', '*.idc', '*.x[bp]m'), ('text/x-chdr', 'text/x-csrc', 'image/x-xbitmap', 'image/x-xpixmap')),
+ 'CMakeLexer': ('pip._vendor.pygments.lexers.make', 'CMake', ('cmake',), ('*.cmake', 'CMakeLists.txt'), ('text/x-cmake',)),
+ 'CObjdumpLexer': ('pip._vendor.pygments.lexers.asm', 'c-objdump', ('c-objdump',), ('*.c-objdump',), ('text/x-c-objdump',)),
+ 'CPSALexer': ('pip._vendor.pygments.lexers.lisp', 'CPSA', ('cpsa',), ('*.cpsa',), ()),
+ 'CSSUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'CSS+UL4', ('css+ul4',), ('*.cssul4',), ()),
+ 'CSharpAspxLexer': ('pip._vendor.pygments.lexers.dotnet', 'aspx-cs', ('aspx-cs',), ('*.aspx', '*.asax', '*.ascx', '*.ashx', '*.asmx', '*.axd'), ()),
+ 'CSharpLexer': ('pip._vendor.pygments.lexers.dotnet', 'C#', ('csharp', 'c#', 'cs'), ('*.cs',), ('text/x-csharp',)),
+ 'Ca65Lexer': ('pip._vendor.pygments.lexers.asm', 'ca65 assembler', ('ca65',), ('*.s',), ()),
+ 'CadlLexer': ('pip._vendor.pygments.lexers.archetype', 'cADL', ('cadl',), ('*.cadl',), ()),
+ 'CapDLLexer': ('pip._vendor.pygments.lexers.esoteric', 'CapDL', ('capdl',), ('*.cdl',), ()),
+ 'CapnProtoLexer': ('pip._vendor.pygments.lexers.capnproto', "Cap'n Proto", ('capnp',), ('*.capnp',), ()),
+ 'CbmBasicV2Lexer': ('pip._vendor.pygments.lexers.basic', 'CBM BASIC V2', ('cbmbas',), ('*.bas',), ()),
+ 'CddlLexer': ('pip._vendor.pygments.lexers.cddl', 'CDDL', ('cddl',), ('*.cddl',), ('text/x-cddl',)),
+ 'CeylonLexer': ('pip._vendor.pygments.lexers.jvm', 'Ceylon', ('ceylon',), ('*.ceylon',), ('text/x-ceylon',)),
+ 'Cfengine3Lexer': ('pip._vendor.pygments.lexers.configs', 'CFEngine3', ('cfengine3', 'cf3'), ('*.cf',), ()),
+ 'ChaiscriptLexer': ('pip._vendor.pygments.lexers.scripting', 'ChaiScript', ('chaiscript', 'chai'), ('*.chai',), ('text/x-chaiscript', 'application/x-chaiscript')),
+ 'ChapelLexer': ('pip._vendor.pygments.lexers.chapel', 'Chapel', ('chapel', 'chpl'), ('*.chpl',), ()),
+ 'CharmciLexer': ('pip._vendor.pygments.lexers.c_like', 'Charmci', ('charmci',), ('*.ci',), ()),
+ 'CheetahHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Cheetah', ('html+cheetah', 'html+spitfire', 'htmlcheetah'), (), ('text/html+cheetah', 'text/html+spitfire')),
+ 'CheetahJavascriptLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Cheetah', ('javascript+cheetah', 'js+cheetah', 'javascript+spitfire', 'js+spitfire'), (), ('application/x-javascript+cheetah', 'text/x-javascript+cheetah', 'text/javascript+cheetah', 'application/x-javascript+spitfire', 'text/x-javascript+spitfire', 'text/javascript+spitfire')),
+ 'CheetahLexer': ('pip._vendor.pygments.lexers.templates', 'Cheetah', ('cheetah', 'spitfire'), ('*.tmpl', '*.spt'), ('application/x-cheetah', 'application/x-spitfire')),
+ 'CheetahXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Cheetah', ('xml+cheetah', 'xml+spitfire'), (), ('application/xml+cheetah', 'application/xml+spitfire')),
+ 'CirruLexer': ('pip._vendor.pygments.lexers.webmisc', 'Cirru', ('cirru',), ('*.cirru',), ('text/x-cirru',)),
+ 'ClayLexer': ('pip._vendor.pygments.lexers.c_like', 'Clay', ('clay',), ('*.clay',), ('text/x-clay',)),
+ 'CleanLexer': ('pip._vendor.pygments.lexers.clean', 'Clean', ('clean',), ('*.icl', '*.dcl'), ()),
+ 'ClojureLexer': ('pip._vendor.pygments.lexers.jvm', 'Clojure', ('clojure', 'clj'), ('*.clj', '*.cljc'), ('text/x-clojure', 'application/x-clojure')),
+ 'ClojureScriptLexer': ('pip._vendor.pygments.lexers.jvm', 'ClojureScript', ('clojurescript', 'cljs'), ('*.cljs',), ('text/x-clojurescript', 'application/x-clojurescript')),
+ 'CobolFreeformatLexer': ('pip._vendor.pygments.lexers.business', 'COBOLFree', ('cobolfree',), ('*.cbl', '*.CBL'), ()),
+ 'CobolLexer': ('pip._vendor.pygments.lexers.business', 'COBOL', ('cobol',), ('*.cob', '*.COB', '*.cpy', '*.CPY'), ('text/x-cobol',)),
+ 'CoffeeScriptLexer': ('pip._vendor.pygments.lexers.javascript', 'CoffeeScript', ('coffeescript', 'coffee-script', 'coffee'), ('*.coffee',), ('text/coffeescript',)),
+ 'ColdfusionCFCLexer': ('pip._vendor.pygments.lexers.templates', 'Coldfusion CFC', ('cfc',), ('*.cfc',), ()),
+ 'ColdfusionHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'Coldfusion HTML', ('cfm',), ('*.cfm', '*.cfml'), ('application/x-coldfusion',)),
+ 'ColdfusionLexer': ('pip._vendor.pygments.lexers.templates', 'cfstatement', ('cfs',), (), ()),
+ 'Comal80Lexer': ('pip._vendor.pygments.lexers.comal', 'COMAL-80', ('comal', 'comal80'), ('*.cml', '*.comal'), ()),
+ 'CommonLispLexer': ('pip._vendor.pygments.lexers.lisp', 'Common Lisp', ('common-lisp', 'cl', 'lisp'), ('*.cl', '*.lisp'), ('text/x-common-lisp',)),
+ 'ComponentPascalLexer': ('pip._vendor.pygments.lexers.oberon', 'Component Pascal', ('componentpascal', 'cp'), ('*.cp', '*.cps'), ('text/x-component-pascal',)),
+ 'CoqLexer': ('pip._vendor.pygments.lexers.theorem', 'Coq', ('coq',), ('*.v',), ('text/x-coq',)),
+ 'CplintLexer': ('pip._vendor.pygments.lexers.cplint', 'cplint', ('cplint',), ('*.ecl', '*.prolog', '*.pro', '*.pl', '*.P', '*.lpad', '*.cpl'), ('text/x-cplint',)),
+ 'CppLexer': ('pip._vendor.pygments.lexers.c_cpp', 'C++', ('cpp', 'c++'), ('*.cpp', '*.hpp', '*.c++', '*.h++', '*.cc', '*.hh', '*.cxx', '*.hxx', '*.C', '*.H', '*.cp', '*.CPP', '*.tpp'), ('text/x-c++hdr', 'text/x-c++src')),
+ 'CppObjdumpLexer': ('pip._vendor.pygments.lexers.asm', 'cpp-objdump', ('cpp-objdump', 'c++-objdumb', 'cxx-objdump'), ('*.cpp-objdump', '*.c++-objdump', '*.cxx-objdump'), ('text/x-cpp-objdump',)),
+ 'CrmshLexer': ('pip._vendor.pygments.lexers.dsls', 'Crmsh', ('crmsh', 'pcmk'), ('*.crmsh', '*.pcmk'), ()),
+ 'CrocLexer': ('pip._vendor.pygments.lexers.d', 'Croc', ('croc',), ('*.croc',), ('text/x-crocsrc',)),
+ 'CryptolLexer': ('pip._vendor.pygments.lexers.haskell', 'Cryptol', ('cryptol', 'cry'), ('*.cry',), ('text/x-cryptol',)),
+ 'CrystalLexer': ('pip._vendor.pygments.lexers.crystal', 'Crystal', ('cr', 'crystal'), ('*.cr',), ('text/x-crystal',)),
+ 'CsoundDocumentLexer': ('pip._vendor.pygments.lexers.csound', 'Csound Document', ('csound-document', 'csound-csd'), ('*.csd',), ()),
+ 'CsoundOrchestraLexer': ('pip._vendor.pygments.lexers.csound', 'Csound Orchestra', ('csound', 'csound-orc'), ('*.orc', '*.udo'), ()),
+ 'CsoundScoreLexer': ('pip._vendor.pygments.lexers.csound', 'Csound Score', ('csound-score', 'csound-sco'), ('*.sco',), ()),
+ 'CssDjangoLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Django/Jinja', ('css+django', 'css+jinja'), ('*.css.j2', '*.css.jinja2'), ('text/css+django', 'text/css+jinja')),
+ 'CssErbLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Ruby', ('css+ruby', 'css+erb'), (), ('text/css+ruby',)),
+ 'CssGenshiLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Genshi Text', ('css+genshitext', 'css+genshi'), (), ('text/css+genshi',)),
+ 'CssLexer': ('pip._vendor.pygments.lexers.css', 'CSS', ('css',), ('*.css',), ('text/css',)),
+ 'CssPhpLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+PHP', ('css+php',), (), ('text/css+php',)),
+ 'CssSmartyLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Smarty', ('css+smarty',), (), ('text/css+smarty',)),
+ 'CudaLexer': ('pip._vendor.pygments.lexers.c_like', 'CUDA', ('cuda', 'cu'), ('*.cu', '*.cuh'), ('text/x-cuda',)),
+ 'CypherLexer': ('pip._vendor.pygments.lexers.graph', 'Cypher', ('cypher',), ('*.cyp', '*.cypher'), ()),
+ 'CythonLexer': ('pip._vendor.pygments.lexers.python', 'Cython', ('cython', 'pyx', 'pyrex'), ('*.pyx', '*.pxd', '*.pxi'), ('text/x-cython', 'application/x-cython')),
+ 'DLexer': ('pip._vendor.pygments.lexers.d', 'D', ('d',), ('*.d', '*.di'), ('text/x-dsrc',)),
+ 'DObjdumpLexer': ('pip._vendor.pygments.lexers.asm', 'd-objdump', ('d-objdump',), ('*.d-objdump',), ('text/x-d-objdump',)),
+ 'DarcsPatchLexer': ('pip._vendor.pygments.lexers.diff', 'Darcs Patch', ('dpatch',), ('*.dpatch', '*.darcspatch'), ()),
+ 'DartLexer': ('pip._vendor.pygments.lexers.javascript', 'Dart', ('dart',), ('*.dart',), ('text/x-dart',)),
+ 'Dasm16Lexer': ('pip._vendor.pygments.lexers.asm', 'DASM16', ('dasm16',), ('*.dasm16', '*.dasm'), ('text/x-dasm16',)),
+ 'DebianControlLexer': ('pip._vendor.pygments.lexers.installers', 'Debian Control file', ('debcontrol', 'control'), ('control',), ()),
+ 'DelphiLexer': ('pip._vendor.pygments.lexers.pascal', 'Delphi', ('delphi', 'pas', 'pascal', 'objectpascal'), ('*.pas', '*.dpr'), ('text/x-pascal',)),
+ 'DevicetreeLexer': ('pip._vendor.pygments.lexers.devicetree', 'Devicetree', ('devicetree', 'dts'), ('*.dts', '*.dtsi'), ('text/x-c',)),
+ 'DgLexer': ('pip._vendor.pygments.lexers.python', 'dg', ('dg',), ('*.dg',), ('text/x-dg',)),
+ 'DiffLexer': ('pip._vendor.pygments.lexers.diff', 'Diff', ('diff', 'udiff'), ('*.diff', '*.patch'), ('text/x-diff', 'text/x-patch')),
+ 'DjangoLexer': ('pip._vendor.pygments.lexers.templates', 'Django/Jinja', ('django', 'jinja'), (), ('application/x-django-templating', 'application/x-jinja')),
+ 'DockerLexer': ('pip._vendor.pygments.lexers.configs', 'Docker', ('docker', 'dockerfile'), ('Dockerfile', '*.docker'), ('text/x-dockerfile-config',)),
+ 'DtdLexer': ('pip._vendor.pygments.lexers.html', 'DTD', ('dtd',), ('*.dtd',), ('application/xml-dtd',)),
+ 'DuelLexer': ('pip._vendor.pygments.lexers.webmisc', 'Duel', ('duel', 'jbst', 'jsonml+bst'), ('*.duel', '*.jbst'), ('text/x-duel', 'text/x-jbst')),
+ 'DylanConsoleLexer': ('pip._vendor.pygments.lexers.dylan', 'Dylan session', ('dylan-console', 'dylan-repl'), ('*.dylan-console',), ('text/x-dylan-console',)),
+ 'DylanLexer': ('pip._vendor.pygments.lexers.dylan', 'Dylan', ('dylan',), ('*.dylan', '*.dyl', '*.intr'), ('text/x-dylan',)),
+ 'DylanLidLexer': ('pip._vendor.pygments.lexers.dylan', 'DylanLID', ('dylan-lid', 'lid'), ('*.lid', '*.hdp'), ('text/x-dylan-lid',)),
+ 'ECLLexer': ('pip._vendor.pygments.lexers.ecl', 'ECL', ('ecl',), ('*.ecl',), ('application/x-ecl',)),
+ 'ECLexer': ('pip._vendor.pygments.lexers.c_like', 'eC', ('ec',), ('*.ec', '*.eh'), ('text/x-echdr', 'text/x-ecsrc')),
+ 'EarlGreyLexer': ('pip._vendor.pygments.lexers.javascript', 'Earl Grey', ('earl-grey', 'earlgrey', 'eg'), ('*.eg',), ('text/x-earl-grey',)),
+ 'EasytrieveLexer': ('pip._vendor.pygments.lexers.scripting', 'Easytrieve', ('easytrieve',), ('*.ezt', '*.mac'), ('text/x-easytrieve',)),
+ 'EbnfLexer': ('pip._vendor.pygments.lexers.parsers', 'EBNF', ('ebnf',), ('*.ebnf',), ('text/x-ebnf',)),
+ 'EiffelLexer': ('pip._vendor.pygments.lexers.eiffel', 'Eiffel', ('eiffel',), ('*.e',), ('text/x-eiffel',)),
+ 'ElixirConsoleLexer': ('pip._vendor.pygments.lexers.erlang', 'Elixir iex session', ('iex',), (), ('text/x-elixir-shellsession',)),
+ 'ElixirLexer': ('pip._vendor.pygments.lexers.erlang', 'Elixir', ('elixir', 'ex', 'exs'), ('*.ex', '*.eex', '*.exs', '*.leex'), ('text/x-elixir',)),
+ 'ElmLexer': ('pip._vendor.pygments.lexers.elm', 'Elm', ('elm',), ('*.elm',), ('text/x-elm',)),
+ 'ElpiLexer': ('pip._vendor.pygments.lexers.elpi', 'Elpi', ('elpi',), ('*.elpi',), ('text/x-elpi',)),
+ 'EmacsLispLexer': ('pip._vendor.pygments.lexers.lisp', 'EmacsLisp', ('emacs-lisp', 'elisp', 'emacs'), ('*.el',), ('text/x-elisp', 'application/x-elisp')),
+ 'EmailLexer': ('pip._vendor.pygments.lexers.email', 'E-mail', ('email', 'eml'), ('*.eml',), ('message/rfc822',)),
+ 'ErbLexer': ('pip._vendor.pygments.lexers.templates', 'ERB', ('erb',), (), ('application/x-ruby-templating',)),
+ 'ErlangLexer': ('pip._vendor.pygments.lexers.erlang', 'Erlang', ('erlang',), ('*.erl', '*.hrl', '*.es', '*.escript'), ('text/x-erlang',)),
+ 'ErlangShellLexer': ('pip._vendor.pygments.lexers.erlang', 'Erlang erl session', ('erl',), ('*.erl-sh',), ('text/x-erl-shellsession',)),
+ 'EvoqueHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Evoque', ('html+evoque',), ('*.html',), ('text/html+evoque',)),
+ 'EvoqueLexer': ('pip._vendor.pygments.lexers.templates', 'Evoque', ('evoque',), ('*.evoque',), ('application/x-evoque',)),
+ 'EvoqueXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Evoque', ('xml+evoque',), ('*.xml',), ('application/xml+evoque',)),
+ 'ExeclineLexer': ('pip._vendor.pygments.lexers.shell', 'execline', ('execline',), ('*.exec',), ()),
+ 'EzhilLexer': ('pip._vendor.pygments.lexers.ezhil', 'Ezhil', ('ezhil',), ('*.n',), ('text/x-ezhil',)),
+ 'FSharpLexer': ('pip._vendor.pygments.lexers.dotnet', 'F#', ('fsharp', 'f#'), ('*.fs', '*.fsi'), ('text/x-fsharp',)),
+ 'FStarLexer': ('pip._vendor.pygments.lexers.ml', 'FStar', ('fstar',), ('*.fst', '*.fsti'), ('text/x-fstar',)),
+ 'FactorLexer': ('pip._vendor.pygments.lexers.factor', 'Factor', ('factor',), ('*.factor',), ('text/x-factor',)),
+ 'FancyLexer': ('pip._vendor.pygments.lexers.ruby', 'Fancy', ('fancy', 'fy'), ('*.fy', '*.fancypack'), ('text/x-fancysrc',)),
+ 'FantomLexer': ('pip._vendor.pygments.lexers.fantom', 'Fantom', ('fan',), ('*.fan',), ('application/x-fantom',)),
+ 'FelixLexer': ('pip._vendor.pygments.lexers.felix', 'Felix', ('felix', 'flx'), ('*.flx', '*.flxh'), ('text/x-felix',)),
+ 'FennelLexer': ('pip._vendor.pygments.lexers.lisp', 'Fennel', ('fennel', 'fnl'), ('*.fnl',), ()),
+ 'FishShellLexer': ('pip._vendor.pygments.lexers.shell', 'Fish', ('fish', 'fishshell'), ('*.fish', '*.load'), ('application/x-fish',)),
+ 'FlatlineLexer': ('pip._vendor.pygments.lexers.dsls', 'Flatline', ('flatline',), (), ('text/x-flatline',)),
+ 'FloScriptLexer': ('pip._vendor.pygments.lexers.floscript', 'FloScript', ('floscript', 'flo'), ('*.flo',), ()),
+ 'ForthLexer': ('pip._vendor.pygments.lexers.forth', 'Forth', ('forth',), ('*.frt', '*.fs'), ('application/x-forth',)),
+ 'FortranFixedLexer': ('pip._vendor.pygments.lexers.fortran', 'FortranFixed', ('fortranfixed',), ('*.f', '*.F'), ()),
+ 'FortranLexer': ('pip._vendor.pygments.lexers.fortran', 'Fortran', ('fortran', 'f90'), ('*.f03', '*.f90', '*.F03', '*.F90'), ('text/x-fortran',)),
+ 'FoxProLexer': ('pip._vendor.pygments.lexers.foxpro', 'FoxPro', ('foxpro', 'vfp', 'clipper', 'xbase'), ('*.PRG', '*.prg'), ()),
+ 'FreeFemLexer': ('pip._vendor.pygments.lexers.freefem', 'Freefem', ('freefem',), ('*.edp',), ('text/x-freefem',)),
+ 'FutharkLexer': ('pip._vendor.pygments.lexers.futhark', 'Futhark', ('futhark',), ('*.fut',), ('text/x-futhark',)),
+ 'GAPLexer': ('pip._vendor.pygments.lexers.algebra', 'GAP', ('gap',), ('*.g', '*.gd', '*.gi', '*.gap'), ()),
+ 'GDScriptLexer': ('pip._vendor.pygments.lexers.gdscript', 'GDScript', ('gdscript', 'gd'), ('*.gd',), ('text/x-gdscript', 'application/x-gdscript')),
+ 'GLShaderLexer': ('pip._vendor.pygments.lexers.graphics', 'GLSL', ('glsl',), ('*.vert', '*.frag', '*.geo'), ('text/x-glslsrc',)),
+ 'GSQLLexer': ('pip._vendor.pygments.lexers.gsql', 'GSQL', ('gsql',), ('*.gsql',), ()),
+ 'GasLexer': ('pip._vendor.pygments.lexers.asm', 'GAS', ('gas', 'asm'), ('*.s', '*.S'), ('text/x-gas',)),
+ 'GcodeLexer': ('pip._vendor.pygments.lexers.gcodelexer', 'g-code', ('gcode',), ('*.gcode',), ()),
+ 'GenshiLexer': ('pip._vendor.pygments.lexers.templates', 'Genshi', ('genshi', 'kid', 'xml+genshi', 'xml+kid'), ('*.kid',), ('application/x-genshi', 'application/x-kid')),
+ 'GenshiTextLexer': ('pip._vendor.pygments.lexers.templates', 'Genshi Text', ('genshitext',), (), ('application/x-genshi-text', 'text/x-genshi')),
+ 'GettextLexer': ('pip._vendor.pygments.lexers.textfmts', 'Gettext Catalog', ('pot', 'po'), ('*.pot', '*.po'), ('application/x-gettext', 'text/x-gettext', 'text/gettext')),
+ 'GherkinLexer': ('pip._vendor.pygments.lexers.testing', 'Gherkin', ('gherkin', 'cucumber'), ('*.feature',), ('text/x-gherkin',)),
+ 'GnuplotLexer': ('pip._vendor.pygments.lexers.graphics', 'Gnuplot', ('gnuplot',), ('*.plot', '*.plt'), ('text/x-gnuplot',)),
+ 'GoLexer': ('pip._vendor.pygments.lexers.go', 'Go', ('go', 'golang'), ('*.go',), ('text/x-gosrc',)),
+ 'GoloLexer': ('pip._vendor.pygments.lexers.jvm', 'Golo', ('golo',), ('*.golo',), ()),
+ 'GoodDataCLLexer': ('pip._vendor.pygments.lexers.business', 'GoodData-CL', ('gooddata-cl',), ('*.gdc',), ('text/x-gooddata-cl',)),
+ 'GosuLexer': ('pip._vendor.pygments.lexers.jvm', 'Gosu', ('gosu',), ('*.gs', '*.gsx', '*.gsp', '*.vark'), ('text/x-gosu',)),
+ 'GosuTemplateLexer': ('pip._vendor.pygments.lexers.jvm', 'Gosu Template', ('gst',), ('*.gst',), ('text/x-gosu-template',)),
+ 'GraphvizLexer': ('pip._vendor.pygments.lexers.graphviz', 'Graphviz', ('graphviz', 'dot'), ('*.gv', '*.dot'), ('text/x-graphviz', 'text/vnd.graphviz')),
+ 'GroffLexer': ('pip._vendor.pygments.lexers.markup', 'Groff', ('groff', 'nroff', 'man'), ('*.[1-9]', '*.man', '*.1p', '*.3pm'), ('application/x-troff', 'text/troff')),
+ 'GroovyLexer': ('pip._vendor.pygments.lexers.jvm', 'Groovy', ('groovy',), ('*.groovy', '*.gradle'), ('text/x-groovy',)),
+ 'HLSLShaderLexer': ('pip._vendor.pygments.lexers.graphics', 'HLSL', ('hlsl',), ('*.hlsl', '*.hlsli'), ('text/x-hlsl',)),
+ 'HTMLUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'HTML+UL4', ('html+ul4',), ('*.htmlul4',), ()),
+ 'HamlLexer': ('pip._vendor.pygments.lexers.html', 'Haml', ('haml',), ('*.haml',), ('text/x-haml',)),
+ 'HandlebarsHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Handlebars', ('html+handlebars',), ('*.handlebars', '*.hbs'), ('text/html+handlebars', 'text/x-handlebars-template')),
+ 'HandlebarsLexer': ('pip._vendor.pygments.lexers.templates', 'Handlebars', ('handlebars',), (), ()),
+ 'HaskellLexer': ('pip._vendor.pygments.lexers.haskell', 'Haskell', ('haskell', 'hs'), ('*.hs',), ('text/x-haskell',)),
+ 'HaxeLexer': ('pip._vendor.pygments.lexers.haxe', 'Haxe', ('haxe', 'hxsl', 'hx'), ('*.hx', '*.hxsl'), ('text/haxe', 'text/x-haxe', 'text/x-hx')),
+ 'HexdumpLexer': ('pip._vendor.pygments.lexers.hexdump', 'Hexdump', ('hexdump',), (), ()),
+ 'HsailLexer': ('pip._vendor.pygments.lexers.asm', 'HSAIL', ('hsail', 'hsa'), ('*.hsail',), ('text/x-hsail',)),
+ 'HspecLexer': ('pip._vendor.pygments.lexers.haskell', 'Hspec', ('hspec',), (), ()),
+ 'HtmlDjangoLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Django/Jinja', ('html+django', 'html+jinja', 'htmldjango'), ('*.html.j2', '*.htm.j2', '*.xhtml.j2', '*.html.jinja2', '*.htm.jinja2', '*.xhtml.jinja2'), ('text/html+django', 'text/html+jinja')),
+ 'HtmlGenshiLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Genshi', ('html+genshi', 'html+kid'), (), ('text/html+genshi',)),
+ 'HtmlLexer': ('pip._vendor.pygments.lexers.html', 'HTML', ('html',), ('*.html', '*.htm', '*.xhtml', '*.xslt'), ('text/html', 'application/xhtml+xml')),
+ 'HtmlPhpLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+PHP', ('html+php',), ('*.phtml',), ('application/x-php', 'application/x-httpd-php', 'application/x-httpd-php3', 'application/x-httpd-php4', 'application/x-httpd-php5')),
+ 'HtmlSmartyLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Smarty', ('html+smarty',), (), ('text/html+smarty',)),
+ 'HttpLexer': ('pip._vendor.pygments.lexers.textfmts', 'HTTP', ('http',), (), ()),
+ 'HxmlLexer': ('pip._vendor.pygments.lexers.haxe', 'Hxml', ('haxeml', 'hxml'), ('*.hxml',), ()),
+ 'HyLexer': ('pip._vendor.pygments.lexers.lisp', 'Hy', ('hylang',), ('*.hy',), ('text/x-hy', 'application/x-hy')),
+ 'HybrisLexer': ('pip._vendor.pygments.lexers.scripting', 'Hybris', ('hybris', 'hy'), ('*.hy', '*.hyb'), ('text/x-hybris', 'application/x-hybris')),
+ 'IDLLexer': ('pip._vendor.pygments.lexers.idl', 'IDL', ('idl',), ('*.pro',), ('text/idl',)),
+ 'IconLexer': ('pip._vendor.pygments.lexers.unicon', 'Icon', ('icon',), ('*.icon', '*.ICON'), ()),
+ 'IdrisLexer': ('pip._vendor.pygments.lexers.haskell', 'Idris', ('idris', 'idr'), ('*.idr',), ('text/x-idris',)),
+ 'IgorLexer': ('pip._vendor.pygments.lexers.igor', 'Igor', ('igor', 'igorpro'), ('*.ipf',), ('text/ipf',)),
+ 'Inform6Lexer': ('pip._vendor.pygments.lexers.int_fiction', 'Inform 6', ('inform6', 'i6'), ('*.inf',), ()),
+ 'Inform6TemplateLexer': ('pip._vendor.pygments.lexers.int_fiction', 'Inform 6 template', ('i6t',), ('*.i6t',), ()),
+ 'Inform7Lexer': ('pip._vendor.pygments.lexers.int_fiction', 'Inform 7', ('inform7', 'i7'), ('*.ni', '*.i7x'), ()),
+ 'IniLexer': ('pip._vendor.pygments.lexers.configs', 'INI', ('ini', 'cfg', 'dosini'), ('*.ini', '*.cfg', '*.inf', '.editorconfig', '*.service', '*.socket', '*.device', '*.mount', '*.automount', '*.swap', '*.target', '*.path', '*.timer', '*.slice', '*.scope'), ('text/x-ini', 'text/inf')),
+ 'IoLexer': ('pip._vendor.pygments.lexers.iolang', 'Io', ('io',), ('*.io',), ('text/x-iosrc',)),
+ 'IokeLexer': ('pip._vendor.pygments.lexers.jvm', 'Ioke', ('ioke', 'ik'), ('*.ik',), ('text/x-iokesrc',)),
+ 'IrcLogsLexer': ('pip._vendor.pygments.lexers.textfmts', 'IRC logs', ('irc',), ('*.weechatlog',), ('text/x-irclog',)),
+ 'IsabelleLexer': ('pip._vendor.pygments.lexers.theorem', 'Isabelle', ('isabelle',), ('*.thy',), ('text/x-isabelle',)),
+ 'JLexer': ('pip._vendor.pygments.lexers.j', 'J', ('j',), ('*.ijs',), ('text/x-j',)),
+ 'JMESPathLexer': ('pip._vendor.pygments.lexers.jmespath', 'JMESPath', ('jmespath', 'jp'), ('*.jp',), ()),
+ 'JSLTLexer': ('pip._vendor.pygments.lexers.jslt', 'JSLT', ('jslt',), ('*.jslt',), ('text/x-jslt',)),
+ 'JagsLexer': ('pip._vendor.pygments.lexers.modeling', 'JAGS', ('jags',), ('*.jag', '*.bug'), ()),
+ 'JasminLexer': ('pip._vendor.pygments.lexers.jvm', 'Jasmin', ('jasmin', 'jasminxt'), ('*.j',), ()),
+ 'JavaLexer': ('pip._vendor.pygments.lexers.jvm', 'Java', ('java',), ('*.java',), ('text/x-java',)),
+ 'JavascriptDjangoLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Django/Jinja', ('javascript+django', 'js+django', 'javascript+jinja', 'js+jinja'), ('*.js.j2', '*.js.jinja2'), ('application/x-javascript+django', 'application/x-javascript+jinja', 'text/x-javascript+django', 'text/x-javascript+jinja', 'text/javascript+django', 'text/javascript+jinja')),
+ 'JavascriptErbLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Ruby', ('javascript+ruby', 'js+ruby', 'javascript+erb', 'js+erb'), (), ('application/x-javascript+ruby', 'text/x-javascript+ruby', 'text/javascript+ruby')),
+ 'JavascriptGenshiLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Genshi Text', ('js+genshitext', 'js+genshi', 'javascript+genshitext', 'javascript+genshi'), (), ('application/x-javascript+genshi', 'text/x-javascript+genshi', 'text/javascript+genshi')),
+ 'JavascriptLexer': ('pip._vendor.pygments.lexers.javascript', 'JavaScript', ('javascript', 'js'), ('*.js', '*.jsm', '*.mjs', '*.cjs'), ('application/javascript', 'application/x-javascript', 'text/x-javascript', 'text/javascript')),
+ 'JavascriptPhpLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+PHP', ('javascript+php', 'js+php'), (), ('application/x-javascript+php', 'text/x-javascript+php', 'text/javascript+php')),
+ 'JavascriptSmartyLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Smarty', ('javascript+smarty', 'js+smarty'), (), ('application/x-javascript+smarty', 'text/x-javascript+smarty', 'text/javascript+smarty')),
+ 'JavascriptUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'Javascript+UL4', ('js+ul4',), ('*.jsul4',), ()),
+ 'JclLexer': ('pip._vendor.pygments.lexers.scripting', 'JCL', ('jcl',), ('*.jcl',), ('text/x-jcl',)),
+ 'JsgfLexer': ('pip._vendor.pygments.lexers.grammar_notation', 'JSGF', ('jsgf',), ('*.jsgf',), ('application/jsgf', 'application/x-jsgf', 'text/jsgf')),
+ 'JsonBareObjectLexer': ('pip._vendor.pygments.lexers.data', 'JSONBareObject', (), (), ()),
+ 'JsonLdLexer': ('pip._vendor.pygments.lexers.data', 'JSON-LD', ('jsonld', 'json-ld'), ('*.jsonld',), ('application/ld+json',)),
+ 'JsonLexer': ('pip._vendor.pygments.lexers.data', 'JSON', ('json', 'json-object'), ('*.json', 'Pipfile.lock'), ('application/json', 'application/json-object')),
+ 'JspLexer': ('pip._vendor.pygments.lexers.templates', 'Java Server Page', ('jsp',), ('*.jsp',), ('application/x-jsp',)),
+ 'JuliaConsoleLexer': ('pip._vendor.pygments.lexers.julia', 'Julia console', ('jlcon', 'julia-repl'), (), ()),
+ 'JuliaLexer': ('pip._vendor.pygments.lexers.julia', 'Julia', ('julia', 'jl'), ('*.jl',), ('text/x-julia', 'application/x-julia')),
+ 'JuttleLexer': ('pip._vendor.pygments.lexers.javascript', 'Juttle', ('juttle',), ('*.juttle',), ('application/juttle', 'application/x-juttle', 'text/x-juttle', 'text/juttle')),
+ 'KLexer': ('pip._vendor.pygments.lexers.q', 'K', ('k',), ('*.k',), ()),
+ 'KalLexer': ('pip._vendor.pygments.lexers.javascript', 'Kal', ('kal',), ('*.kal',), ('text/kal', 'application/kal')),
+ 'KconfigLexer': ('pip._vendor.pygments.lexers.configs', 'Kconfig', ('kconfig', 'menuconfig', 'linux-config', 'kernel-config'), ('Kconfig*', '*Config.in*', 'external.in*', 'standard-modules.in'), ('text/x-kconfig',)),
+ 'KernelLogLexer': ('pip._vendor.pygments.lexers.textfmts', 'Kernel log', ('kmsg', 'dmesg'), ('*.kmsg', '*.dmesg'), ()),
+ 'KokaLexer': ('pip._vendor.pygments.lexers.haskell', 'Koka', ('koka',), ('*.kk', '*.kki'), ('text/x-koka',)),
+ 'KotlinLexer': ('pip._vendor.pygments.lexers.jvm', 'Kotlin', ('kotlin',), ('*.kt', '*.kts'), ('text/x-kotlin',)),
+ 'KuinLexer': ('pip._vendor.pygments.lexers.kuin', 'Kuin', ('kuin',), ('*.kn',), ()),
+ 'LSLLexer': ('pip._vendor.pygments.lexers.scripting', 'LSL', ('lsl',), ('*.lsl',), ('text/x-lsl',)),
+ 'LassoCssLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Lasso', ('css+lasso',), (), ('text/css+lasso',)),
+ 'LassoHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Lasso', ('html+lasso',), (), ('text/html+lasso', 'application/x-httpd-lasso', 'application/x-httpd-lasso[89]')),
+ 'LassoJavascriptLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Lasso', ('javascript+lasso', 'js+lasso'), (), ('application/x-javascript+lasso', 'text/x-javascript+lasso', 'text/javascript+lasso')),
+ 'LassoLexer': ('pip._vendor.pygments.lexers.javascript', 'Lasso', ('lasso', 'lassoscript'), ('*.lasso', '*.lasso[89]'), ('text/x-lasso',)),
+ 'LassoXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Lasso', ('xml+lasso',), (), ('application/xml+lasso',)),
+ 'LeanLexer': ('pip._vendor.pygments.lexers.theorem', 'Lean', ('lean',), ('*.lean',), ('text/x-lean',)),
+ 'LessCssLexer': ('pip._vendor.pygments.lexers.css', 'LessCss', ('less',), ('*.less',), ('text/x-less-css',)),
+ 'LighttpdConfLexer': ('pip._vendor.pygments.lexers.configs', 'Lighttpd configuration file', ('lighttpd', 'lighty'), ('lighttpd.conf',), ('text/x-lighttpd-conf',)),
+ 'LilyPondLexer': ('pip._vendor.pygments.lexers.lilypond', 'LilyPond', ('lilypond',), ('*.ly',), ()),
+ 'LimboLexer': ('pip._vendor.pygments.lexers.inferno', 'Limbo', ('limbo',), ('*.b',), ('text/limbo',)),
+ 'LiquidLexer': ('pip._vendor.pygments.lexers.templates', 'liquid', ('liquid',), ('*.liquid',), ()),
+ 'LiterateAgdaLexer': ('pip._vendor.pygments.lexers.haskell', 'Literate Agda', ('literate-agda', 'lagda'), ('*.lagda',), ('text/x-literate-agda',)),
+ 'LiterateCryptolLexer': ('pip._vendor.pygments.lexers.haskell', 'Literate Cryptol', ('literate-cryptol', 'lcryptol', 'lcry'), ('*.lcry',), ('text/x-literate-cryptol',)),
+ 'LiterateHaskellLexer': ('pip._vendor.pygments.lexers.haskell', 'Literate Haskell', ('literate-haskell', 'lhaskell', 'lhs'), ('*.lhs',), ('text/x-literate-haskell',)),
+ 'LiterateIdrisLexer': ('pip._vendor.pygments.lexers.haskell', 'Literate Idris', ('literate-idris', 'lidris', 'lidr'), ('*.lidr',), ('text/x-literate-idris',)),
+ 'LiveScriptLexer': ('pip._vendor.pygments.lexers.javascript', 'LiveScript', ('livescript', 'live-script'), ('*.ls',), ('text/livescript',)),
+ 'LlvmLexer': ('pip._vendor.pygments.lexers.asm', 'LLVM', ('llvm',), ('*.ll',), ('text/x-llvm',)),
+ 'LlvmMirBodyLexer': ('pip._vendor.pygments.lexers.asm', 'LLVM-MIR Body', ('llvm-mir-body',), (), ()),
+ 'LlvmMirLexer': ('pip._vendor.pygments.lexers.asm', 'LLVM-MIR', ('llvm-mir',), ('*.mir',), ()),
+ 'LogosLexer': ('pip._vendor.pygments.lexers.objective', 'Logos', ('logos',), ('*.x', '*.xi', '*.xm', '*.xmi'), ('text/x-logos',)),
+ 'LogtalkLexer': ('pip._vendor.pygments.lexers.prolog', 'Logtalk', ('logtalk',), ('*.lgt', '*.logtalk'), ('text/x-logtalk',)),
+ 'LuaLexer': ('pip._vendor.pygments.lexers.scripting', 'Lua', ('lua',), ('*.lua', '*.wlua'), ('text/x-lua', 'application/x-lua')),
+ 'MCFunctionLexer': ('pip._vendor.pygments.lexers.mcfunction', 'MCFunction', ('mcfunction', 'mcf'), ('*.mcfunction',), ('text/mcfunction',)),
+ 'MIMELexer': ('pip._vendor.pygments.lexers.mime', 'MIME', ('mime',), (), ('multipart/mixed', 'multipart/related', 'multipart/alternative')),
+ 'MOOCodeLexer': ('pip._vendor.pygments.lexers.scripting', 'MOOCode', ('moocode', 'moo'), ('*.moo',), ('text/x-moocode',)),
+ 'MSDOSSessionLexer': ('pip._vendor.pygments.lexers.shell', 'MSDOS Session', ('doscon',), (), ()),
+ 'Macaulay2Lexer': ('pip._vendor.pygments.lexers.macaulay2', 'Macaulay2', ('macaulay2',), ('*.m2',), ()),
+ 'MakefileLexer': ('pip._vendor.pygments.lexers.make', 'Makefile', ('make', 'makefile', 'mf', 'bsdmake'), ('*.mak', '*.mk', 'Makefile', 'makefile', 'Makefile.*', 'GNUmakefile'), ('text/x-makefile',)),
+ 'MakoCssLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Mako', ('css+mako',), (), ('text/css+mako',)),
+ 'MakoHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Mako', ('html+mako',), (), ('text/html+mako',)),
+ 'MakoJavascriptLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Mako', ('javascript+mako', 'js+mako'), (), ('application/x-javascript+mako', 'text/x-javascript+mako', 'text/javascript+mako')),
+ 'MakoLexer': ('pip._vendor.pygments.lexers.templates', 'Mako', ('mako',), ('*.mao',), ('application/x-mako',)),
+ 'MakoXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Mako', ('xml+mako',), (), ('application/xml+mako',)),
+ 'MaqlLexer': ('pip._vendor.pygments.lexers.business', 'MAQL', ('maql',), ('*.maql',), ('text/x-gooddata-maql', 'application/x-gooddata-maql')),
+ 'MarkdownLexer': ('pip._vendor.pygments.lexers.markup', 'Markdown', ('markdown', 'md'), ('*.md', '*.markdown'), ('text/x-markdown',)),
+ 'MaskLexer': ('pip._vendor.pygments.lexers.javascript', 'Mask', ('mask',), ('*.mask',), ('text/x-mask',)),
+ 'MasonLexer': ('pip._vendor.pygments.lexers.templates', 'Mason', ('mason',), ('*.m', '*.mhtml', '*.mc', '*.mi', 'autohandler', 'dhandler'), ('application/x-mason',)),
+ 'MathematicaLexer': ('pip._vendor.pygments.lexers.algebra', 'Mathematica', ('mathematica', 'mma', 'nb'), ('*.nb', '*.cdf', '*.nbp', '*.ma'), ('application/mathematica', 'application/vnd.wolfram.mathematica', 'application/vnd.wolfram.mathematica.package', 'application/vnd.wolfram.cdf')),
+ 'MatlabLexer': ('pip._vendor.pygments.lexers.matlab', 'Matlab', ('matlab',), ('*.m',), ('text/matlab',)),
+ 'MatlabSessionLexer': ('pip._vendor.pygments.lexers.matlab', 'Matlab session', ('matlabsession',), (), ()),
+ 'MaximaLexer': ('pip._vendor.pygments.lexers.maxima', 'Maxima', ('maxima', 'macsyma'), ('*.mac', '*.max'), ()),
+ 'MesonLexer': ('pip._vendor.pygments.lexers.meson', 'Meson', ('meson', 'meson.build'), ('meson.build', 'meson_options.txt'), ('text/x-meson',)),
+ 'MiniDLexer': ('pip._vendor.pygments.lexers.d', 'MiniD', ('minid',), (), ('text/x-minidsrc',)),
+ 'MiniScriptLexer': ('pip._vendor.pygments.lexers.scripting', 'MiniScript', ('miniscript', 'ms'), ('*.ms',), ('text/x-minicript', 'application/x-miniscript')),
+ 'ModelicaLexer': ('pip._vendor.pygments.lexers.modeling', 'Modelica', ('modelica',), ('*.mo',), ('text/x-modelica',)),
+ 'Modula2Lexer': ('pip._vendor.pygments.lexers.modula2', 'Modula-2', ('modula2', 'm2'), ('*.def', '*.mod'), ('text/x-modula2',)),
+ 'MoinWikiLexer': ('pip._vendor.pygments.lexers.markup', 'MoinMoin/Trac Wiki markup', ('trac-wiki', 'moin'), (), ('text/x-trac-wiki',)),
+ 'MonkeyLexer': ('pip._vendor.pygments.lexers.basic', 'Monkey', ('monkey',), ('*.monkey',), ('text/x-monkey',)),
+ 'MonteLexer': ('pip._vendor.pygments.lexers.monte', 'Monte', ('monte',), ('*.mt',), ()),
+ 'MoonScriptLexer': ('pip._vendor.pygments.lexers.scripting', 'MoonScript', ('moonscript', 'moon'), ('*.moon',), ('text/x-moonscript', 'application/x-moonscript')),
+ 'MoselLexer': ('pip._vendor.pygments.lexers.mosel', 'Mosel', ('mosel',), ('*.mos',), ()),
+ 'MozPreprocCssLexer': ('pip._vendor.pygments.lexers.markup', 'CSS+mozpreproc', ('css+mozpreproc',), ('*.css.in',), ()),
+ 'MozPreprocHashLexer': ('pip._vendor.pygments.lexers.markup', 'mozhashpreproc', ('mozhashpreproc',), (), ()),
+ 'MozPreprocJavascriptLexer': ('pip._vendor.pygments.lexers.markup', 'Javascript+mozpreproc', ('javascript+mozpreproc',), ('*.js.in',), ()),
+ 'MozPreprocPercentLexer': ('pip._vendor.pygments.lexers.markup', 'mozpercentpreproc', ('mozpercentpreproc',), (), ()),
+ 'MozPreprocXulLexer': ('pip._vendor.pygments.lexers.markup', 'XUL+mozpreproc', ('xul+mozpreproc',), ('*.xul.in',), ()),
+ 'MqlLexer': ('pip._vendor.pygments.lexers.c_like', 'MQL', ('mql', 'mq4', 'mq5', 'mql4', 'mql5'), ('*.mq4', '*.mq5', '*.mqh'), ('text/x-mql',)),
+ 'MscgenLexer': ('pip._vendor.pygments.lexers.dsls', 'Mscgen', ('mscgen', 'msc'), ('*.msc',), ()),
+ 'MuPADLexer': ('pip._vendor.pygments.lexers.algebra', 'MuPAD', ('mupad',), ('*.mu',), ()),
+ 'MxmlLexer': ('pip._vendor.pygments.lexers.actionscript', 'MXML', ('mxml',), ('*.mxml',), ()),
+ 'MySqlLexer': ('pip._vendor.pygments.lexers.sql', 'MySQL', ('mysql',), (), ('text/x-mysql',)),
+ 'MyghtyCssLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Myghty', ('css+myghty',), (), ('text/css+myghty',)),
+ 'MyghtyHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Myghty', ('html+myghty',), (), ('text/html+myghty',)),
+ 'MyghtyJavascriptLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Myghty', ('javascript+myghty', 'js+myghty'), (), ('application/x-javascript+myghty', 'text/x-javascript+myghty', 'text/javascript+mygthy')),
+ 'MyghtyLexer': ('pip._vendor.pygments.lexers.templates', 'Myghty', ('myghty',), ('*.myt', 'autodelegate'), ('application/x-myghty',)),
+ 'MyghtyXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Myghty', ('xml+myghty',), (), ('application/xml+myghty',)),
+ 'NCLLexer': ('pip._vendor.pygments.lexers.ncl', 'NCL', ('ncl',), ('*.ncl',), ('text/ncl',)),
+ 'NSISLexer': ('pip._vendor.pygments.lexers.installers', 'NSIS', ('nsis', 'nsi', 'nsh'), ('*.nsi', '*.nsh'), ('text/x-nsis',)),
+ 'NasmLexer': ('pip._vendor.pygments.lexers.asm', 'NASM', ('nasm',), ('*.asm', '*.ASM'), ('text/x-nasm',)),
+ 'NasmObjdumpLexer': ('pip._vendor.pygments.lexers.asm', 'objdump-nasm', ('objdump-nasm',), ('*.objdump-intel',), ('text/x-nasm-objdump',)),
+ 'NemerleLexer': ('pip._vendor.pygments.lexers.dotnet', 'Nemerle', ('nemerle',), ('*.n',), ('text/x-nemerle',)),
+ 'NesCLexer': ('pip._vendor.pygments.lexers.c_like', 'nesC', ('nesc',), ('*.nc',), ('text/x-nescsrc',)),
+ 'NestedTextLexer': ('pip._vendor.pygments.lexers.configs', 'NestedText', ('nestedtext', 'nt'), ('*.nt',), ()),
+ 'NewLispLexer': ('pip._vendor.pygments.lexers.lisp', 'NewLisp', ('newlisp',), ('*.lsp', '*.nl', '*.kif'), ('text/x-newlisp', 'application/x-newlisp')),
+ 'NewspeakLexer': ('pip._vendor.pygments.lexers.smalltalk', 'Newspeak', ('newspeak',), ('*.ns2',), ('text/x-newspeak',)),
+ 'NginxConfLexer': ('pip._vendor.pygments.lexers.configs', 'Nginx configuration file', ('nginx',), ('nginx.conf',), ('text/x-nginx-conf',)),
+ 'NimrodLexer': ('pip._vendor.pygments.lexers.nimrod', 'Nimrod', ('nimrod', 'nim'), ('*.nim', '*.nimrod'), ('text/x-nim',)),
+ 'NitLexer': ('pip._vendor.pygments.lexers.nit', 'Nit', ('nit',), ('*.nit',), ()),
+ 'NixLexer': ('pip._vendor.pygments.lexers.nix', 'Nix', ('nixos', 'nix'), ('*.nix',), ('text/x-nix',)),
+ 'NodeConsoleLexer': ('pip._vendor.pygments.lexers.javascript', 'Node.js REPL console session', ('nodejsrepl',), (), ('text/x-nodejsrepl',)),
+ 'NotmuchLexer': ('pip._vendor.pygments.lexers.textfmts', 'Notmuch', ('notmuch',), (), ()),
+ 'NuSMVLexer': ('pip._vendor.pygments.lexers.smv', 'NuSMV', ('nusmv',), ('*.smv',), ()),
+ 'NumPyLexer': ('pip._vendor.pygments.lexers.python', 'NumPy', ('numpy',), (), ()),
+ 'ObjdumpLexer': ('pip._vendor.pygments.lexers.asm', 'objdump', ('objdump',), ('*.objdump',), ('text/x-objdump',)),
+ 'ObjectiveCLexer': ('pip._vendor.pygments.lexers.objective', 'Objective-C', ('objective-c', 'objectivec', 'obj-c', 'objc'), ('*.m', '*.h'), ('text/x-objective-c',)),
+ 'ObjectiveCppLexer': ('pip._vendor.pygments.lexers.objective', 'Objective-C++', ('objective-c++', 'objectivec++', 'obj-c++', 'objc++'), ('*.mm', '*.hh'), ('text/x-objective-c++',)),
+ 'ObjectiveJLexer': ('pip._vendor.pygments.lexers.javascript', 'Objective-J', ('objective-j', 'objectivej', 'obj-j', 'objj'), ('*.j',), ('text/x-objective-j',)),
+ 'OcamlLexer': ('pip._vendor.pygments.lexers.ml', 'OCaml', ('ocaml',), ('*.ml', '*.mli', '*.mll', '*.mly'), ('text/x-ocaml',)),
+ 'OctaveLexer': ('pip._vendor.pygments.lexers.matlab', 'Octave', ('octave',), ('*.m',), ('text/octave',)),
+ 'OdinLexer': ('pip._vendor.pygments.lexers.archetype', 'ODIN', ('odin',), ('*.odin',), ('text/odin',)),
+ 'OmgIdlLexer': ('pip._vendor.pygments.lexers.c_like', 'OMG Interface Definition Language', ('omg-idl',), ('*.idl', '*.pidl'), ()),
+ 'OocLexer': ('pip._vendor.pygments.lexers.ooc', 'Ooc', ('ooc',), ('*.ooc',), ('text/x-ooc',)),
+ 'OpaLexer': ('pip._vendor.pygments.lexers.ml', 'Opa', ('opa',), ('*.opa',), ('text/x-opa',)),
+ 'OpenEdgeLexer': ('pip._vendor.pygments.lexers.business', 'OpenEdge ABL', ('openedge', 'abl', 'progress'), ('*.p', '*.cls'), ('text/x-openedge', 'application/x-openedge')),
+ 'OutputLexer': ('pip._vendor.pygments.lexers.special', 'Text output', ('output',), (), ()),
+ 'PacmanConfLexer': ('pip._vendor.pygments.lexers.configs', 'PacmanConf', ('pacmanconf',), ('pacman.conf',), ()),
+ 'PanLexer': ('pip._vendor.pygments.lexers.dsls', 'Pan', ('pan',), ('*.pan',), ()),
+ 'ParaSailLexer': ('pip._vendor.pygments.lexers.parasail', 'ParaSail', ('parasail',), ('*.psi', '*.psl'), ('text/x-parasail',)),
+ 'PawnLexer': ('pip._vendor.pygments.lexers.pawn', 'Pawn', ('pawn',), ('*.p', '*.pwn', '*.inc'), ('text/x-pawn',)),
+ 'PegLexer': ('pip._vendor.pygments.lexers.grammar_notation', 'PEG', ('peg',), ('*.peg',), ('text/x-peg',)),
+ 'Perl6Lexer': ('pip._vendor.pygments.lexers.perl', 'Perl6', ('perl6', 'pl6', 'raku'), ('*.pl', '*.pm', '*.nqp', '*.p6', '*.6pl', '*.p6l', '*.pl6', '*.6pm', '*.p6m', '*.pm6', '*.t', '*.raku', '*.rakumod', '*.rakutest', '*.rakudoc'), ('text/x-perl6', 'application/x-perl6')),
+ 'PerlLexer': ('pip._vendor.pygments.lexers.perl', 'Perl', ('perl', 'pl'), ('*.pl', '*.pm', '*.t', '*.perl'), ('text/x-perl', 'application/x-perl')),
+ 'PhpLexer': ('pip._vendor.pygments.lexers.php', 'PHP', ('php', 'php3', 'php4', 'php5'), ('*.php', '*.php[345]', '*.inc'), ('text/x-php',)),
+ 'PigLexer': ('pip._vendor.pygments.lexers.jvm', 'Pig', ('pig',), ('*.pig',), ('text/x-pig',)),
+ 'PikeLexer': ('pip._vendor.pygments.lexers.c_like', 'Pike', ('pike',), ('*.pike', '*.pmod'), ('text/x-pike',)),
+ 'PkgConfigLexer': ('pip._vendor.pygments.lexers.configs', 'PkgConfig', ('pkgconfig',), ('*.pc',), ()),
+ 'PlPgsqlLexer': ('pip._vendor.pygments.lexers.sql', 'PL/pgSQL', ('plpgsql',), (), ('text/x-plpgsql',)),
+ 'PointlessLexer': ('pip._vendor.pygments.lexers.pointless', 'Pointless', ('pointless',), ('*.ptls',), ()),
+ 'PonyLexer': ('pip._vendor.pygments.lexers.pony', 'Pony', ('pony',), ('*.pony',), ()),
+ 'PostScriptLexer': ('pip._vendor.pygments.lexers.graphics', 'PostScript', ('postscript', 'postscr'), ('*.ps', '*.eps'), ('application/postscript',)),
+ 'PostgresConsoleLexer': ('pip._vendor.pygments.lexers.sql', 'PostgreSQL console (psql)', ('psql', 'postgresql-console', 'postgres-console'), (), ('text/x-postgresql-psql',)),
+ 'PostgresLexer': ('pip._vendor.pygments.lexers.sql', 'PostgreSQL SQL dialect', ('postgresql', 'postgres'), (), ('text/x-postgresql',)),
+ 'PovrayLexer': ('pip._vendor.pygments.lexers.graphics', 'POVRay', ('pov',), ('*.pov', '*.inc'), ('text/x-povray',)),
+ 'PowerShellLexer': ('pip._vendor.pygments.lexers.shell', 'PowerShell', ('powershell', 'pwsh', 'posh', 'ps1', 'psm1'), ('*.ps1', '*.psm1'), ('text/x-powershell',)),
+ 'PowerShellSessionLexer': ('pip._vendor.pygments.lexers.shell', 'PowerShell Session', ('pwsh-session', 'ps1con'), (), ()),
+ 'PraatLexer': ('pip._vendor.pygments.lexers.praat', 'Praat', ('praat',), ('*.praat', '*.proc', '*.psc'), ()),
+ 'ProcfileLexer': ('pip._vendor.pygments.lexers.procfile', 'Procfile', ('procfile',), ('Procfile',), ()),
+ 'PrologLexer': ('pip._vendor.pygments.lexers.prolog', 'Prolog', ('prolog',), ('*.ecl', '*.prolog', '*.pro', '*.pl'), ('text/x-prolog',)),
+ 'PromQLLexer': ('pip._vendor.pygments.lexers.promql', 'PromQL', ('promql',), ('*.promql',), ()),
+ 'PropertiesLexer': ('pip._vendor.pygments.lexers.configs', 'Properties', ('properties', 'jproperties'), ('*.properties',), ('text/x-java-properties',)),
+ 'ProtoBufLexer': ('pip._vendor.pygments.lexers.dsls', 'Protocol Buffer', ('protobuf', 'proto'), ('*.proto',), ()),
+ 'PsyshConsoleLexer': ('pip._vendor.pygments.lexers.php', 'PsySH console session for PHP', ('psysh',), (), ()),
+ 'PugLexer': ('pip._vendor.pygments.lexers.html', 'Pug', ('pug', 'jade'), ('*.pug', '*.jade'), ('text/x-pug', 'text/x-jade')),
+ 'PuppetLexer': ('pip._vendor.pygments.lexers.dsls', 'Puppet', ('puppet',), ('*.pp',), ()),
+ 'PyPyLogLexer': ('pip._vendor.pygments.lexers.console', 'PyPy Log', ('pypylog', 'pypy'), ('*.pypylog',), ('application/x-pypylog',)),
+ 'Python2Lexer': ('pip._vendor.pygments.lexers.python', 'Python 2.x', ('python2', 'py2'), (), ('text/x-python2', 'application/x-python2')),
+ 'Python2TracebackLexer': ('pip._vendor.pygments.lexers.python', 'Python 2.x Traceback', ('py2tb',), ('*.py2tb',), ('text/x-python2-traceback',)),
+ 'PythonConsoleLexer': ('pip._vendor.pygments.lexers.python', 'Python console session', ('pycon',), (), ('text/x-python-doctest',)),
+ 'PythonLexer': ('pip._vendor.pygments.lexers.python', 'Python', ('python', 'py', 'sage', 'python3', 'py3'), ('*.py', '*.pyw', '*.jy', '*.sage', '*.sc', 'SConstruct', 'SConscript', '*.bzl', 'BUCK', 'BUILD', 'BUILD.bazel', 'WORKSPACE', '*.tac'), ('text/x-python', 'application/x-python', 'text/x-python3', 'application/x-python3')),
+ 'PythonTracebackLexer': ('pip._vendor.pygments.lexers.python', 'Python Traceback', ('pytb', 'py3tb'), ('*.pytb', '*.py3tb'), ('text/x-python-traceback', 'text/x-python3-traceback')),
+ 'PythonUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'Python+UL4', ('py+ul4',), ('*.pyul4',), ()),
+ 'QBasicLexer': ('pip._vendor.pygments.lexers.basic', 'QBasic', ('qbasic', 'basic'), ('*.BAS', '*.bas'), ('text/basic',)),
+ 'QLexer': ('pip._vendor.pygments.lexers.q', 'Q', ('q',), ('*.q',), ()),
+ 'QVToLexer': ('pip._vendor.pygments.lexers.qvt', 'QVTO', ('qvto', 'qvt'), ('*.qvto',), ()),
+ 'QlikLexer': ('pip._vendor.pygments.lexers.qlik', 'Qlik', ('qlik', 'qlikview', 'qliksense', 'qlikscript'), ('*.qvs', '*.qvw'), ()),
+ 'QmlLexer': ('pip._vendor.pygments.lexers.webmisc', 'QML', ('qml', 'qbs'), ('*.qml', '*.qbs'), ('application/x-qml', 'application/x-qt.qbs+qml')),
+ 'RConsoleLexer': ('pip._vendor.pygments.lexers.r', 'RConsole', ('rconsole', 'rout'), ('*.Rout',), ()),
+ 'RNCCompactLexer': ('pip._vendor.pygments.lexers.rnc', 'Relax-NG Compact', ('rng-compact', 'rnc'), ('*.rnc',), ()),
+ 'RPMSpecLexer': ('pip._vendor.pygments.lexers.installers', 'RPMSpec', ('spec',), ('*.spec',), ('text/x-rpm-spec',)),
+ 'RacketLexer': ('pip._vendor.pygments.lexers.lisp', 'Racket', ('racket', 'rkt'), ('*.rkt', '*.rktd', '*.rktl'), ('text/x-racket', 'application/x-racket')),
+ 'RagelCLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in C Host', ('ragel-c',), ('*.rl',), ()),
+ 'RagelCppLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in CPP Host', ('ragel-cpp',), ('*.rl',), ()),
+ 'RagelDLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in D Host', ('ragel-d',), ('*.rl',), ()),
+ 'RagelEmbeddedLexer': ('pip._vendor.pygments.lexers.parsers', 'Embedded Ragel', ('ragel-em',), ('*.rl',), ()),
+ 'RagelJavaLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in Java Host', ('ragel-java',), ('*.rl',), ()),
+ 'RagelLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel', ('ragel',), (), ()),
+ 'RagelObjectiveCLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in Objective C Host', ('ragel-objc',), ('*.rl',), ()),
+ 'RagelRubyLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in Ruby Host', ('ragel-ruby', 'ragel-rb'), ('*.rl',), ()),
+ 'RawTokenLexer': ('pip._vendor.pygments.lexers.special', 'Raw token data', (), (), ('application/x-pygments-tokens',)),
+ 'RdLexer': ('pip._vendor.pygments.lexers.r', 'Rd', ('rd',), ('*.Rd',), ('text/x-r-doc',)),
+ 'ReasonLexer': ('pip._vendor.pygments.lexers.ml', 'ReasonML', ('reasonml', 'reason'), ('*.re', '*.rei'), ('text/x-reasonml',)),
+ 'RebolLexer': ('pip._vendor.pygments.lexers.rebol', 'REBOL', ('rebol',), ('*.r', '*.r3', '*.reb'), ('text/x-rebol',)),
+ 'RedLexer': ('pip._vendor.pygments.lexers.rebol', 'Red', ('red', 'red/system'), ('*.red', '*.reds'), ('text/x-red', 'text/x-red-system')),
+ 'RedcodeLexer': ('pip._vendor.pygments.lexers.esoteric', 'Redcode', ('redcode',), ('*.cw',), ()),
+ 'RegeditLexer': ('pip._vendor.pygments.lexers.configs', 'reg', ('registry',), ('*.reg',), ('text/x-windows-registry',)),
+ 'ResourceLexer': ('pip._vendor.pygments.lexers.resource', 'ResourceBundle', ('resourcebundle', 'resource'), (), ()),
+ 'RexxLexer': ('pip._vendor.pygments.lexers.scripting', 'Rexx', ('rexx', 'arexx'), ('*.rexx', '*.rex', '*.rx', '*.arexx'), ('text/x-rexx',)),
+ 'RhtmlLexer': ('pip._vendor.pygments.lexers.templates', 'RHTML', ('rhtml', 'html+erb', 'html+ruby'), ('*.rhtml',), ('text/html+ruby',)),
+ 'RideLexer': ('pip._vendor.pygments.lexers.ride', 'Ride', ('ride',), ('*.ride',), ('text/x-ride',)),
+ 'RitaLexer': ('pip._vendor.pygments.lexers.rita', 'Rita', ('rita',), ('*.rita',), ('text/rita',)),
+ 'RoboconfGraphLexer': ('pip._vendor.pygments.lexers.roboconf', 'Roboconf Graph', ('roboconf-graph',), ('*.graph',), ()),
+ 'RoboconfInstancesLexer': ('pip._vendor.pygments.lexers.roboconf', 'Roboconf Instances', ('roboconf-instances',), ('*.instances',), ()),
+ 'RobotFrameworkLexer': ('pip._vendor.pygments.lexers.robotframework', 'RobotFramework', ('robotframework',), ('*.robot', '*.resource'), ('text/x-robotframework',)),
+ 'RqlLexer': ('pip._vendor.pygments.lexers.sql', 'RQL', ('rql',), ('*.rql',), ('text/x-rql',)),
+ 'RslLexer': ('pip._vendor.pygments.lexers.dsls', 'RSL', ('rsl',), ('*.rsl',), ('text/rsl',)),
+ 'RstLexer': ('pip._vendor.pygments.lexers.markup', 'reStructuredText', ('restructuredtext', 'rst', 'rest'), ('*.rst', '*.rest'), ('text/x-rst', 'text/prs.fallenstein.rst')),
+ 'RtsLexer': ('pip._vendor.pygments.lexers.trafficscript', 'TrafficScript', ('trafficscript', 'rts'), ('*.rts',), ()),
+ 'RubyConsoleLexer': ('pip._vendor.pygments.lexers.ruby', 'Ruby irb session', ('rbcon', 'irb'), (), ('text/x-ruby-shellsession',)),
+ 'RubyLexer': ('pip._vendor.pygments.lexers.ruby', 'Ruby', ('ruby', 'rb', 'duby'), ('*.rb', '*.rbw', 'Rakefile', '*.rake', '*.gemspec', '*.rbx', '*.duby', 'Gemfile', 'Vagrantfile'), ('text/x-ruby', 'application/x-ruby')),
+ 'RustLexer': ('pip._vendor.pygments.lexers.rust', 'Rust', ('rust', 'rs'), ('*.rs', '*.rs.in'), ('text/rust', 'text/x-rust')),
+ 'SASLexer': ('pip._vendor.pygments.lexers.sas', 'SAS', ('sas',), ('*.SAS', '*.sas'), ('text/x-sas', 'text/sas', 'application/x-sas')),
+ 'SLexer': ('pip._vendor.pygments.lexers.r', 'S', ('splus', 's', 'r'), ('*.S', '*.R', '.Rhistory', '.Rprofile', '.Renviron'), ('text/S-plus', 'text/S', 'text/x-r-source', 'text/x-r', 'text/x-R', 'text/x-r-history', 'text/x-r-profile')),
+ 'SMLLexer': ('pip._vendor.pygments.lexers.ml', 'Standard ML', ('sml',), ('*.sml', '*.sig', '*.fun'), ('text/x-standardml', 'application/x-standardml')),
+ 'SNBTLexer': ('pip._vendor.pygments.lexers.mcfunction', 'SNBT', ('snbt',), ('*.snbt',), ('text/snbt',)),
+ 'SarlLexer': ('pip._vendor.pygments.lexers.jvm', 'SARL', ('sarl',), ('*.sarl',), ('text/x-sarl',)),
+ 'SassLexer': ('pip._vendor.pygments.lexers.css', 'Sass', ('sass',), ('*.sass',), ('text/x-sass',)),
+ 'SaviLexer': ('pip._vendor.pygments.lexers.savi', 'Savi', ('savi',), ('*.savi',), ()),
+ 'ScalaLexer': ('pip._vendor.pygments.lexers.jvm', 'Scala', ('scala',), ('*.scala',), ('text/x-scala',)),
+ 'ScamlLexer': ('pip._vendor.pygments.lexers.html', 'Scaml', ('scaml',), ('*.scaml',), ('text/x-scaml',)),
+ 'ScdocLexer': ('pip._vendor.pygments.lexers.scdoc', 'scdoc', ('scdoc', 'scd'), ('*.scd', '*.scdoc'), ()),
+ 'SchemeLexer': ('pip._vendor.pygments.lexers.lisp', 'Scheme', ('scheme', 'scm'), ('*.scm', '*.ss'), ('text/x-scheme', 'application/x-scheme')),
+ 'ScilabLexer': ('pip._vendor.pygments.lexers.matlab', 'Scilab', ('scilab',), ('*.sci', '*.sce', '*.tst'), ('text/scilab',)),
+ 'ScssLexer': ('pip._vendor.pygments.lexers.css', 'SCSS', ('scss',), ('*.scss',), ('text/x-scss',)),
+ 'SedLexer': ('pip._vendor.pygments.lexers.textedit', 'Sed', ('sed', 'gsed', 'ssed'), ('*.sed', '*.[gs]sed'), ('text/x-sed',)),
+ 'ShExCLexer': ('pip._vendor.pygments.lexers.rdf', 'ShExC', ('shexc', 'shex'), ('*.shex',), ('text/shex',)),
+ 'ShenLexer': ('pip._vendor.pygments.lexers.lisp', 'Shen', ('shen',), ('*.shen',), ('text/x-shen', 'application/x-shen')),
+ 'SieveLexer': ('pip._vendor.pygments.lexers.sieve', 'Sieve', ('sieve',), ('*.siv', '*.sieve'), ()),
+ 'SilverLexer': ('pip._vendor.pygments.lexers.verification', 'Silver', ('silver',), ('*.sil', '*.vpr'), ()),
+ 'SingularityLexer': ('pip._vendor.pygments.lexers.configs', 'Singularity', ('singularity',), ('*.def', 'Singularity'), ()),
+ 'SlashLexer': ('pip._vendor.pygments.lexers.slash', 'Slash', ('slash',), ('*.sla',), ()),
+ 'SlimLexer': ('pip._vendor.pygments.lexers.webmisc', 'Slim', ('slim',), ('*.slim',), ('text/x-slim',)),
+ 'SlurmBashLexer': ('pip._vendor.pygments.lexers.shell', 'Slurm', ('slurm', 'sbatch'), ('*.sl',), ()),
+ 'SmaliLexer': ('pip._vendor.pygments.lexers.dalvik', 'Smali', ('smali',), ('*.smali',), ('text/smali',)),
+ 'SmalltalkLexer': ('pip._vendor.pygments.lexers.smalltalk', 'Smalltalk', ('smalltalk', 'squeak', 'st'), ('*.st',), ('text/x-smalltalk',)),
+ 'SmartGameFormatLexer': ('pip._vendor.pygments.lexers.sgf', 'SmartGameFormat', ('sgf',), ('*.sgf',), ()),
+ 'SmartyLexer': ('pip._vendor.pygments.lexers.templates', 'Smarty', ('smarty',), ('*.tpl',), ('application/x-smarty',)),
+ 'SmithyLexer': ('pip._vendor.pygments.lexers.smithy', 'Smithy', ('smithy',), ('*.smithy',), ()),
+ 'SnobolLexer': ('pip._vendor.pygments.lexers.snobol', 'Snobol', ('snobol',), ('*.snobol',), ('text/x-snobol',)),
+ 'SnowballLexer': ('pip._vendor.pygments.lexers.dsls', 'Snowball', ('snowball',), ('*.sbl',), ()),
+ 'SolidityLexer': ('pip._vendor.pygments.lexers.solidity', 'Solidity', ('solidity',), ('*.sol',), ()),
+ 'SophiaLexer': ('pip._vendor.pygments.lexers.sophia', 'Sophia', ('sophia',), ('*.aes',), ()),
+ 'SourcePawnLexer': ('pip._vendor.pygments.lexers.pawn', 'SourcePawn', ('sp',), ('*.sp',), ('text/x-sourcepawn',)),
+ 'SourcesListLexer': ('pip._vendor.pygments.lexers.installers', 'Debian Sourcelist', ('debsources', 'sourceslist', 'sources.list'), ('sources.list',), ()),
+ 'SparqlLexer': ('pip._vendor.pygments.lexers.rdf', 'SPARQL', ('sparql',), ('*.rq', '*.sparql'), ('application/sparql-query',)),
+ 'SpiceLexer': ('pip._vendor.pygments.lexers.spice', 'Spice', ('spice', 'spicelang'), ('*.spice',), ('text/x-spice',)),
+ 'SqlJinjaLexer': ('pip._vendor.pygments.lexers.templates', 'SQL+Jinja', ('sql+jinja',), ('*.sql', '*.sql.j2', '*.sql.jinja2'), ()),
+ 'SqlLexer': ('pip._vendor.pygments.lexers.sql', 'SQL', ('sql',), ('*.sql',), ('text/x-sql',)),
+ 'SqliteConsoleLexer': ('pip._vendor.pygments.lexers.sql', 'sqlite3con', ('sqlite3',), ('*.sqlite3-console',), ('text/x-sqlite3-console',)),
+ 'SquidConfLexer': ('pip._vendor.pygments.lexers.configs', 'SquidConf', ('squidconf', 'squid.conf', 'squid'), ('squid.conf',), ('text/x-squidconf',)),
+ 'SrcinfoLexer': ('pip._vendor.pygments.lexers.srcinfo', 'Srcinfo', ('srcinfo',), ('.SRCINFO',), ()),
+ 'SspLexer': ('pip._vendor.pygments.lexers.templates', 'Scalate Server Page', ('ssp',), ('*.ssp',), ('application/x-ssp',)),
+ 'StanLexer': ('pip._vendor.pygments.lexers.modeling', 'Stan', ('stan',), ('*.stan',), ()),
+ 'StataLexer': ('pip._vendor.pygments.lexers.stata', 'Stata', ('stata', 'do'), ('*.do', '*.ado'), ('text/x-stata', 'text/stata', 'application/x-stata')),
+ 'SuperColliderLexer': ('pip._vendor.pygments.lexers.supercollider', 'SuperCollider', ('supercollider', 'sc'), ('*.sc', '*.scd'), ('application/supercollider', 'text/supercollider')),
+ 'SwiftLexer': ('pip._vendor.pygments.lexers.objective', 'Swift', ('swift',), ('*.swift',), ('text/x-swift',)),
+ 'SwigLexer': ('pip._vendor.pygments.lexers.c_like', 'SWIG', ('swig',), ('*.swg', '*.i'), ('text/swig',)),
+ 'SystemVerilogLexer': ('pip._vendor.pygments.lexers.hdl', 'systemverilog', ('systemverilog', 'sv'), ('*.sv', '*.svh'), ('text/x-systemverilog',)),
+ 'TAPLexer': ('pip._vendor.pygments.lexers.testing', 'TAP', ('tap',), ('*.tap',), ()),
+ 'TNTLexer': ('pip._vendor.pygments.lexers.tnt', 'Typographic Number Theory', ('tnt',), ('*.tnt',), ()),
+ 'TOMLLexer': ('pip._vendor.pygments.lexers.configs', 'TOML', ('toml',), ('*.toml', 'Pipfile', 'poetry.lock'), ()),
+ 'Tads3Lexer': ('pip._vendor.pygments.lexers.int_fiction', 'TADS 3', ('tads3',), ('*.t',), ()),
+ 'TalLexer': ('pip._vendor.pygments.lexers.tal', 'Tal', ('tal', 'uxntal'), ('*.tal',), ('text/x-uxntal',)),
+ 'TasmLexer': ('pip._vendor.pygments.lexers.asm', 'TASM', ('tasm',), ('*.asm', '*.ASM', '*.tasm'), ('text/x-tasm',)),
+ 'TclLexer': ('pip._vendor.pygments.lexers.tcl', 'Tcl', ('tcl',), ('*.tcl', '*.rvt'), ('text/x-tcl', 'text/x-script.tcl', 'application/x-tcl')),
+ 'TcshLexer': ('pip._vendor.pygments.lexers.shell', 'Tcsh', ('tcsh', 'csh'), ('*.tcsh', '*.csh'), ('application/x-csh',)),
+ 'TcshSessionLexer': ('pip._vendor.pygments.lexers.shell', 'Tcsh Session', ('tcshcon',), (), ()),
+ 'TeaTemplateLexer': ('pip._vendor.pygments.lexers.templates', 'Tea', ('tea',), ('*.tea',), ('text/x-tea',)),
+ 'TealLexer': ('pip._vendor.pygments.lexers.teal', 'teal', ('teal',), ('*.teal',), ()),
+ 'TeraTermLexer': ('pip._vendor.pygments.lexers.teraterm', 'Tera Term macro', ('teratermmacro', 'teraterm', 'ttl'), ('*.ttl',), ('text/x-teratermmacro',)),
+ 'TermcapLexer': ('pip._vendor.pygments.lexers.configs', 'Termcap', ('termcap',), ('termcap', 'termcap.src'), ()),
+ 'TerminfoLexer': ('pip._vendor.pygments.lexers.configs', 'Terminfo', ('terminfo',), ('terminfo', 'terminfo.src'), ()),
+ 'TerraformLexer': ('pip._vendor.pygments.lexers.configs', 'Terraform', ('terraform', 'tf'), ('*.tf',), ('application/x-tf', 'application/x-terraform')),
+ 'TexLexer': ('pip._vendor.pygments.lexers.markup', 'TeX', ('tex', 'latex'), ('*.tex', '*.aux', '*.toc'), ('text/x-tex', 'text/x-latex')),
+ 'TextLexer': ('pip._vendor.pygments.lexers.special', 'Text only', ('text',), ('*.txt',), ('text/plain',)),
+ 'ThingsDBLexer': ('pip._vendor.pygments.lexers.thingsdb', 'ThingsDB', ('ti', 'thingsdb'), ('*.ti',), ()),
+ 'ThriftLexer': ('pip._vendor.pygments.lexers.dsls', 'Thrift', ('thrift',), ('*.thrift',), ('application/x-thrift',)),
+ 'TiddlyWiki5Lexer': ('pip._vendor.pygments.lexers.markup', 'tiddler', ('tid',), ('*.tid',), ('text/vnd.tiddlywiki',)),
+ 'TodotxtLexer': ('pip._vendor.pygments.lexers.textfmts', 'Todotxt', ('todotxt',), ('todo.txt', '*.todotxt'), ('text/x-todo',)),
+ 'TransactSqlLexer': ('pip._vendor.pygments.lexers.sql', 'Transact-SQL', ('tsql', 't-sql'), ('*.sql',), ('text/x-tsql',)),
+ 'TreetopLexer': ('pip._vendor.pygments.lexers.parsers', 'Treetop', ('treetop',), ('*.treetop', '*.tt'), ()),
+ 'TurtleLexer': ('pip._vendor.pygments.lexers.rdf', 'Turtle', ('turtle',), ('*.ttl',), ('text/turtle', 'application/x-turtle')),
+ 'TwigHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Twig', ('html+twig',), ('*.twig',), ('text/html+twig',)),
+ 'TwigLexer': ('pip._vendor.pygments.lexers.templates', 'Twig', ('twig',), (), ('application/x-twig',)),
+ 'TypeScriptLexer': ('pip._vendor.pygments.lexers.javascript', 'TypeScript', ('typescript', 'ts'), ('*.ts',), ('application/x-typescript', 'text/x-typescript')),
+ 'TypoScriptCssDataLexer': ('pip._vendor.pygments.lexers.typoscript', 'TypoScriptCssData', ('typoscriptcssdata',), (), ()),
+ 'TypoScriptHtmlDataLexer': ('pip._vendor.pygments.lexers.typoscript', 'TypoScriptHtmlData', ('typoscripthtmldata',), (), ()),
+ 'TypoScriptLexer': ('pip._vendor.pygments.lexers.typoscript', 'TypoScript', ('typoscript',), ('*.typoscript',), ('text/x-typoscript',)),
+ 'UL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'UL4', ('ul4',), ('*.ul4',), ()),
+ 'UcodeLexer': ('pip._vendor.pygments.lexers.unicon', 'ucode', ('ucode',), ('*.u', '*.u1', '*.u2'), ()),
+ 'UniconLexer': ('pip._vendor.pygments.lexers.unicon', 'Unicon', ('unicon',), ('*.icn',), ('text/unicon',)),
+ 'UnixConfigLexer': ('pip._vendor.pygments.lexers.configs', 'Unix/Linux config files', ('unixconfig', 'linuxconfig'), (), ()),
+ 'UrbiscriptLexer': ('pip._vendor.pygments.lexers.urbi', 'UrbiScript', ('urbiscript',), ('*.u',), ('application/x-urbiscript',)),
+ 'UsdLexer': ('pip._vendor.pygments.lexers.usd', 'USD', ('usd', 'usda'), ('*.usd', '*.usda'), ()),
+ 'VBScriptLexer': ('pip._vendor.pygments.lexers.basic', 'VBScript', ('vbscript',), ('*.vbs', '*.VBS'), ()),
+ 'VCLLexer': ('pip._vendor.pygments.lexers.varnish', 'VCL', ('vcl',), ('*.vcl',), ('text/x-vclsrc',)),
+ 'VCLSnippetLexer': ('pip._vendor.pygments.lexers.varnish', 'VCLSnippets', ('vclsnippets', 'vclsnippet'), (), ('text/x-vclsnippet',)),
+ 'VCTreeStatusLexer': ('pip._vendor.pygments.lexers.console', 'VCTreeStatus', ('vctreestatus',), (), ()),
+ 'VGLLexer': ('pip._vendor.pygments.lexers.dsls', 'VGL', ('vgl',), ('*.rpf',), ()),
+ 'ValaLexer': ('pip._vendor.pygments.lexers.c_like', 'Vala', ('vala', 'vapi'), ('*.vala', '*.vapi'), ('text/x-vala',)),
+ 'VbNetAspxLexer': ('pip._vendor.pygments.lexers.dotnet', 'aspx-vb', ('aspx-vb',), ('*.aspx', '*.asax', '*.ascx', '*.ashx', '*.asmx', '*.axd'), ()),
+ 'VbNetLexer': ('pip._vendor.pygments.lexers.dotnet', 'VB.net', ('vb.net', 'vbnet', 'lobas', 'oobas', 'sobas'), ('*.vb', '*.bas'), ('text/x-vbnet', 'text/x-vba')),
+ 'VelocityHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Velocity', ('html+velocity',), (), ('text/html+velocity',)),
+ 'VelocityLexer': ('pip._vendor.pygments.lexers.templates', 'Velocity', ('velocity',), ('*.vm', '*.fhtml'), ()),
+ 'VelocityXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Velocity', ('xml+velocity',), (), ('application/xml+velocity',)),
+ 'VerilogLexer': ('pip._vendor.pygments.lexers.hdl', 'verilog', ('verilog', 'v'), ('*.v',), ('text/x-verilog',)),
+ 'VhdlLexer': ('pip._vendor.pygments.lexers.hdl', 'vhdl', ('vhdl',), ('*.vhdl', '*.vhd'), ('text/x-vhdl',)),
+ 'VimLexer': ('pip._vendor.pygments.lexers.textedit', 'VimL', ('vim',), ('*.vim', '.vimrc', '.exrc', '.gvimrc', '_vimrc', '_exrc', '_gvimrc', 'vimrc', 'gvimrc'), ('text/x-vim',)),
+ 'WDiffLexer': ('pip._vendor.pygments.lexers.diff', 'WDiff', ('wdiff',), ('*.wdiff',), ()),
+ 'WatLexer': ('pip._vendor.pygments.lexers.webassembly', 'WebAssembly', ('wast', 'wat'), ('*.wat', '*.wast'), ()),
+ 'WebIDLLexer': ('pip._vendor.pygments.lexers.webidl', 'Web IDL', ('webidl',), ('*.webidl',), ()),
+ 'WhileyLexer': ('pip._vendor.pygments.lexers.whiley', 'Whiley', ('whiley',), ('*.whiley',), ('text/x-whiley',)),
+ 'X10Lexer': ('pip._vendor.pygments.lexers.x10', 'X10', ('x10', 'xten'), ('*.x10',), ('text/x-x10',)),
+ 'XMLUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'XML+UL4', ('xml+ul4',), ('*.xmlul4',), ()),
+ 'XQueryLexer': ('pip._vendor.pygments.lexers.webmisc', 'XQuery', ('xquery', 'xqy', 'xq', 'xql', 'xqm'), ('*.xqy', '*.xquery', '*.xq', '*.xql', '*.xqm'), ('text/xquery', 'application/xquery')),
+ 'XmlDjangoLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Django/Jinja', ('xml+django', 'xml+jinja'), ('*.xml.j2', '*.xml.jinja2'), ('application/xml+django', 'application/xml+jinja')),
+ 'XmlErbLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Ruby', ('xml+ruby', 'xml+erb'), (), ('application/xml+ruby',)),
+ 'XmlLexer': ('pip._vendor.pygments.lexers.html', 'XML', ('xml',), ('*.xml', '*.xsl', '*.rss', '*.xslt', '*.xsd', '*.wsdl', '*.wsf'), ('text/xml', 'application/xml', 'image/svg+xml', 'application/rss+xml', 'application/atom+xml')),
+ 'XmlPhpLexer': ('pip._vendor.pygments.lexers.templates', 'XML+PHP', ('xml+php',), (), ('application/xml+php',)),
+ 'XmlSmartyLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Smarty', ('xml+smarty',), (), ('application/xml+smarty',)),
+ 'XorgLexer': ('pip._vendor.pygments.lexers.xorg', 'Xorg', ('xorg.conf',), ('xorg.conf',), ()),
+ 'XsltLexer': ('pip._vendor.pygments.lexers.html', 'XSLT', ('xslt',), ('*.xsl', '*.xslt', '*.xpl'), ('application/xsl+xml', 'application/xslt+xml')),
+ 'XtendLexer': ('pip._vendor.pygments.lexers.jvm', 'Xtend', ('xtend',), ('*.xtend',), ('text/x-xtend',)),
+ 'XtlangLexer': ('pip._vendor.pygments.lexers.lisp', 'xtlang', ('extempore',), ('*.xtm',), ()),
+ 'YamlJinjaLexer': ('pip._vendor.pygments.lexers.templates', 'YAML+Jinja', ('yaml+jinja', 'salt', 'sls'), ('*.sls', '*.yaml.j2', '*.yml.j2', '*.yaml.jinja2', '*.yml.jinja2'), ('text/x-yaml+jinja', 'text/x-sls')),
+ 'YamlLexer': ('pip._vendor.pygments.lexers.data', 'YAML', ('yaml',), ('*.yaml', '*.yml'), ('text/x-yaml',)),
+ 'YangLexer': ('pip._vendor.pygments.lexers.yang', 'YANG', ('yang',), ('*.yang',), ('application/yang',)),
+ 'ZeekLexer': ('pip._vendor.pygments.lexers.dsls', 'Zeek', ('zeek', 'bro'), ('*.zeek', '*.bro'), ()),
+ 'ZephirLexer': ('pip._vendor.pygments.lexers.php', 'Zephir', ('zephir',), ('*.zep',), ()),
+ 'ZigLexer': ('pip._vendor.pygments.lexers.zig', 'Zig', ('zig',), ('*.zig',), ('text/zig',)),
+ 'apdlexer': ('pip._vendor.pygments.lexers.apdlexer', 'ANSYS parametric design language', ('ansys', 'apdl'), ('*.ans',), ()),
+}
diff --git a/src/pip/_vendor/pygments/lexers/python.py b/src/pip/_vendor/pygments/lexers/python.py
new file mode 100644
index 000000000..c24e3c86e
--- /dev/null
+++ b/src/pip/_vendor/pygments/lexers/python.py
@@ -0,0 +1,1204 @@
+"""
+ pygments.lexers.python
+ ~~~~~~~~~~~~~~~~~~~~~~
+
+ Lexers for Python and related languages.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import re
+import keyword
+
+from pip._vendor.pygments.lexer import Lexer, RegexLexer, include, bygroups, using, \
+ default, words, combined, do_insertions, this
+from pip._vendor.pygments.util import get_bool_opt, shebang_matches
+from pip._vendor.pygments.token import Text, Comment, Operator, Keyword, Name, String, \
+ Number, Punctuation, Generic, Other, Error
+from pip._vendor.pygments import unistring as uni
+
+__all__ = ['PythonLexer', 'PythonConsoleLexer', 'PythonTracebackLexer',
+ 'Python2Lexer', 'Python2TracebackLexer',
+ 'CythonLexer', 'DgLexer', 'NumPyLexer']
+
+line_re = re.compile('.*?\n')
+
+
+class PythonLexer(RegexLexer):
+ """
+ For Python source code (version 3.x).
+
+ .. versionadded:: 0.10
+
+ .. versionchanged:: 2.5
+ This is now the default ``PythonLexer``. It is still available as the
+ alias ``Python3Lexer``.
+ """
+
+ name = 'Python'
+ url = 'http://www.python.org'
+ aliases = ['python', 'py', 'sage', 'python3', 'py3']
+ filenames = [
+ '*.py',
+ '*.pyw',
+ # Jython
+ '*.jy',
+ # Sage
+ '*.sage',
+ # SCons
+ '*.sc',
+ 'SConstruct',
+ 'SConscript',
+ # Skylark/Starlark (used by Bazel, Buck, and Pants)
+ '*.bzl',
+ 'BUCK',
+ 'BUILD',
+ 'BUILD.bazel',
+ 'WORKSPACE',
+ # Twisted Application infrastructure
+ '*.tac',
+ ]
+ mimetypes = ['text/x-python', 'application/x-python',
+ 'text/x-python3', 'application/x-python3']
+
+ uni_name = "[%s][%s]*" % (uni.xid_start, uni.xid_continue)
+
+ def innerstring_rules(ttype):
+ return [
+ # the old style '%s' % (...) string formatting (still valid in Py3)
+ (r'%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?'
+ '[hlL]?[E-GXc-giorsaux%]', String.Interpol),
+ # the new style '{}'.format(...) string formatting
+ (r'\{'
+ r'((\w+)((\.\w+)|(\[[^\]]+\]))*)?' # field name
+ r'(\![sra])?' # conversion
+ r'(\:(.?[<>=\^])?[-+ ]?#?0?(\d+)?,?(\.\d+)?[E-GXb-gnosx%]?)?'
+ r'\}', String.Interpol),
+
+ # backslashes, quotes and formatting signs must be parsed one at a time
+ (r'[^\\\'"%{\n]+', ttype),
+ (r'[\'"\\]', ttype),
+ # unhandled string formatting sign
+ (r'%|(\{{1,2})', ttype)
+ # newlines are an error (use "nl" state)
+ ]
+
+ def fstring_rules(ttype):
+ return [
+ # Assuming that a '}' is the closing brace after format specifier.
+ # Sadly, this means that we won't detect syntax error. But it's
+ # more important to parse correct syntax correctly, than to
+ # highlight invalid syntax.
+ (r'\}', String.Interpol),
+ (r'\{', String.Interpol, 'expr-inside-fstring'),
+ # backslashes, quotes and formatting signs must be parsed one at a time
+ (r'[^\\\'"{}\n]+', ttype),
+ (r'[\'"\\]', ttype),
+ # newlines are an error (use "nl" state)
+ ]
+
+ tokens = {
+ 'root': [
+ (r'\n', Text),
+ (r'^(\s*)([rRuUbB]{,2})("""(?:.|\n)*?""")',
+ bygroups(Text, String.Affix, String.Doc)),
+ (r"^(\s*)([rRuUbB]{,2})('''(?:.|\n)*?''')",
+ bygroups(Text, String.Affix, String.Doc)),
+ (r'\A#!.+$', Comment.Hashbang),
+ (r'#.*$', Comment.Single),
+ (r'\\\n', Text),
+ (r'\\', Text),
+ include('keywords'),
+ include('soft-keywords'),
+ (r'(def)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'funcname'),
+ (r'(class)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'classname'),
+ (r'(from)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text),
+ 'fromimport'),
+ (r'(import)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text),
+ 'import'),
+ include('expr'),
+ ],
+ 'expr': [
+ # raw f-strings
+ ('(?i)(rf|fr)(""")',
+ bygroups(String.Affix, String.Double),
+ combined('rfstringescape', 'tdqf')),
+ ("(?i)(rf|fr)(''')",
+ bygroups(String.Affix, String.Single),
+ combined('rfstringescape', 'tsqf')),
+ ('(?i)(rf|fr)(")',
+ bygroups(String.Affix, String.Double),
+ combined('rfstringescape', 'dqf')),
+ ("(?i)(rf|fr)(')",
+ bygroups(String.Affix, String.Single),
+ combined('rfstringescape', 'sqf')),
+ # non-raw f-strings
+ ('([fF])(""")', bygroups(String.Affix, String.Double),
+ combined('fstringescape', 'tdqf')),
+ ("([fF])(''')", bygroups(String.Affix, String.Single),
+ combined('fstringescape', 'tsqf')),
+ ('([fF])(")', bygroups(String.Affix, String.Double),
+ combined('fstringescape', 'dqf')),
+ ("([fF])(')", bygroups(String.Affix, String.Single),
+ combined('fstringescape', 'sqf')),
+ # raw bytes and strings
+ ('(?i)(rb|br|r)(""")',
+ bygroups(String.Affix, String.Double), 'tdqs'),
+ ("(?i)(rb|br|r)(''')",
+ bygroups(String.Affix, String.Single), 'tsqs'),
+ ('(?i)(rb|br|r)(")',
+ bygroups(String.Affix, String.Double), 'dqs'),
+ ("(?i)(rb|br|r)(')",
+ bygroups(String.Affix, String.Single), 'sqs'),
+ # non-raw strings
+ ('([uU]?)(""")', bygroups(String.Affix, String.Double),
+ combined('stringescape', 'tdqs')),
+ ("([uU]?)(''')", bygroups(String.Affix, String.Single),
+ combined('stringescape', 'tsqs')),
+ ('([uU]?)(")', bygroups(String.Affix, String.Double),
+ combined('stringescape', 'dqs')),
+ ("([uU]?)(')", bygroups(String.Affix, String.Single),
+ combined('stringescape', 'sqs')),
+ # non-raw bytes
+ ('([bB])(""")', bygroups(String.Affix, String.Double),
+ combined('bytesescape', 'tdqs')),
+ ("([bB])(''')", bygroups(String.Affix, String.Single),
+ combined('bytesescape', 'tsqs')),
+ ('([bB])(")', bygroups(String.Affix, String.Double),
+ combined('bytesescape', 'dqs')),
+ ("([bB])(')", bygroups(String.Affix, String.Single),
+ combined('bytesescape', 'sqs')),
+
+ (r'[^\S\n]+', Text),
+ include('numbers'),
+ (r'!=|==|<<|>>|:=|[-~+/*%=<>&^|.]', Operator),
+ (r'[]{}:(),;[]', Punctuation),
+ (r'(in|is|and|or|not)\b', Operator.Word),
+ include('expr-keywords'),
+ include('builtins'),
+ include('magicfuncs'),
+ include('magicvars'),
+ include('name'),
+ ],
+ 'expr-inside-fstring': [
+ (r'[{([]', Punctuation, 'expr-inside-fstring-inner'),
+ # without format specifier
+ (r'(=\s*)?' # debug (https://bugs.python.org/issue36817)
+ r'(\![sraf])?' # conversion
+ r'\}', String.Interpol, '#pop'),
+ # with format specifier
+ # we'll catch the remaining '}' in the outer scope
+ (r'(=\s*)?' # debug (https://bugs.python.org/issue36817)
+ r'(\![sraf])?' # conversion
+ r':', String.Interpol, '#pop'),
+ (r'\s+', Text), # allow new lines
+ include('expr'),
+ ],
+ 'expr-inside-fstring-inner': [
+ (r'[{([]', Punctuation, 'expr-inside-fstring-inner'),
+ (r'[])}]', Punctuation, '#pop'),
+ (r'\s+', Text), # allow new lines
+ include('expr'),
+ ],
+ 'expr-keywords': [
+ # Based on https://docs.python.org/3/reference/expressions.html
+ (words((
+ 'async for', 'await', 'else', 'for', 'if', 'lambda',
+ 'yield', 'yield from'), suffix=r'\b'),
+ Keyword),
+ (words(('True', 'False', 'None'), suffix=r'\b'), Keyword.Constant),
+ ],
+ 'keywords': [
+ (words((
+ 'assert', 'async', 'await', 'break', 'continue', 'del', 'elif',
+ 'else', 'except', 'finally', 'for', 'global', 'if', 'lambda',
+ 'pass', 'raise', 'nonlocal', 'return', 'try', 'while', 'yield',
+ 'yield from', 'as', 'with'), suffix=r'\b'),
+ Keyword),
+ (words(('True', 'False', 'None'), suffix=r'\b'), Keyword.Constant),
+ ],
+ 'soft-keywords': [
+ # `match`, `case` and `_` soft keywords
+ (r'(^[ \t]*)' # at beginning of line + possible indentation
+ r'(match|case)\b' # a possible keyword
+ r'(?![ \t]*(?:' # not followed by...
+ r'[:,;=^&|@~)\]}]|(?:' + # characters and keywords that mean this isn't
+ r'|'.join(keyword.kwlist) + r')\b))', # pattern matching
+ bygroups(Text, Keyword), 'soft-keywords-inner'),
+ ],
+ 'soft-keywords-inner': [
+ # optional `_` keyword
+ (r'(\s+)([^\n_]*)(_\b)', bygroups(Text, using(this), Keyword)),
+ default('#pop')
+ ],
+ 'builtins': [
+ (words((
+ '__import__', 'abs', 'all', 'any', 'bin', 'bool', 'bytearray',
+ 'breakpoint', 'bytes', 'chr', 'classmethod', 'compile', 'complex',
+ 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'filter',
+ 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr',
+ 'hash', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass',
+ 'iter', 'len', 'list', 'locals', 'map', 'max', 'memoryview',
+ 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print',
+ 'property', 'range', 'repr', 'reversed', 'round', 'set', 'setattr',
+ 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple',
+ 'type', 'vars', 'zip'), prefix=r'(?<!\.)', suffix=r'\b'),
+ Name.Builtin),
+ (r'(?<!\.)(self|Ellipsis|NotImplemented|cls)\b', Name.Builtin.Pseudo),
+ (words((
+ 'ArithmeticError', 'AssertionError', 'AttributeError',
+ 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning',
+ 'EOFError', 'EnvironmentError', 'Exception', 'FloatingPointError',
+ 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
+ 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError',
+ 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError',
+ 'NotImplementedError', 'OSError', 'OverflowError',
+ 'PendingDeprecationWarning', 'ReferenceError', 'ResourceWarning',
+ 'RuntimeError', 'RuntimeWarning', 'StopIteration',
+ 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit',
+ 'TabError', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',
+ 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError',
+ 'UnicodeWarning', 'UserWarning', 'ValueError', 'VMSError',
+ 'Warning', 'WindowsError', 'ZeroDivisionError',
+ # new builtin exceptions from PEP 3151
+ 'BlockingIOError', 'ChildProcessError', 'ConnectionError',
+ 'BrokenPipeError', 'ConnectionAbortedError', 'ConnectionRefusedError',
+ 'ConnectionResetError', 'FileExistsError', 'FileNotFoundError',
+ 'InterruptedError', 'IsADirectoryError', 'NotADirectoryError',
+ 'PermissionError', 'ProcessLookupError', 'TimeoutError',
+ # others new in Python 3
+ 'StopAsyncIteration', 'ModuleNotFoundError', 'RecursionError',
+ 'EncodingWarning'),
+ prefix=r'(?<!\.)', suffix=r'\b'),
+ Name.Exception),
+ ],
+ 'magicfuncs': [
+ (words((
+ '__abs__', '__add__', '__aenter__', '__aexit__', '__aiter__',
+ '__and__', '__anext__', '__await__', '__bool__', '__bytes__',
+ '__call__', '__complex__', '__contains__', '__del__', '__delattr__',
+ '__delete__', '__delitem__', '__dir__', '__divmod__', '__enter__',
+ '__eq__', '__exit__', '__float__', '__floordiv__', '__format__',
+ '__ge__', '__get__', '__getattr__', '__getattribute__',
+ '__getitem__', '__gt__', '__hash__', '__iadd__', '__iand__',
+ '__ifloordiv__', '__ilshift__', '__imatmul__', '__imod__',
+ '__imul__', '__index__', '__init__', '__instancecheck__',
+ '__int__', '__invert__', '__ior__', '__ipow__', '__irshift__',
+ '__isub__', '__iter__', '__itruediv__', '__ixor__', '__le__',
+ '__len__', '__length_hint__', '__lshift__', '__lt__', '__matmul__',
+ '__missing__', '__mod__', '__mul__', '__ne__', '__neg__',
+ '__new__', '__next__', '__or__', '__pos__', '__pow__',
+ '__prepare__', '__radd__', '__rand__', '__rdivmod__', '__repr__',
+ '__reversed__', '__rfloordiv__', '__rlshift__', '__rmatmul__',
+ '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__',
+ '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__',
+ '__rxor__', '__set__', '__setattr__', '__setitem__', '__str__',
+ '__sub__', '__subclasscheck__', '__truediv__',
+ '__xor__'), suffix=r'\b'),
+ Name.Function.Magic),
+ ],
+ 'magicvars': [
+ (words((
+ '__annotations__', '__bases__', '__class__', '__closure__',
+ '__code__', '__defaults__', '__dict__', '__doc__', '__file__',
+ '__func__', '__globals__', '__kwdefaults__', '__module__',
+ '__mro__', '__name__', '__objclass__', '__qualname__',
+ '__self__', '__slots__', '__weakref__'), suffix=r'\b'),
+ Name.Variable.Magic),
+ ],
+ 'numbers': [
+ (r'(\d(?:_?\d)*\.(?:\d(?:_?\d)*)?|(?:\d(?:_?\d)*)?\.\d(?:_?\d)*)'
+ r'([eE][+-]?\d(?:_?\d)*)?', Number.Float),
+ (r'\d(?:_?\d)*[eE][+-]?\d(?:_?\d)*j?', Number.Float),
+ (r'0[oO](?:_?[0-7])+', Number.Oct),
+ (r'0[bB](?:_?[01])+', Number.Bin),
+ (r'0[xX](?:_?[a-fA-F0-9])+', Number.Hex),
+ (r'\d(?:_?\d)*', Number.Integer),
+ ],
+ 'name': [
+ (r'@' + uni_name, Name.Decorator),
+ (r'@', Operator), # new matrix multiplication operator
+ (uni_name, Name),
+ ],
+ 'funcname': [
+ include('magicfuncs'),
+ (uni_name, Name.Function, '#pop'),
+ default('#pop'),
+ ],
+ 'classname': [
+ (uni_name, Name.Class, '#pop'),
+ ],
+ 'import': [
+ (r'(\s+)(as)(\s+)', bygroups(Text, Keyword, Text)),
+ (r'\.', Name.Namespace),
+ (uni_name, Name.Namespace),
+ (r'(\s*)(,)(\s*)', bygroups(Text, Operator, Text)),
+ default('#pop') # all else: go back
+ ],
+ 'fromimport': [
+ (r'(\s+)(import)\b', bygroups(Text, Keyword.Namespace), '#pop'),
+ (r'\.', Name.Namespace),
+ # if None occurs here, it's "raise x from None", since None can
+ # never be a module name
+ (r'None\b', Name.Builtin.Pseudo, '#pop'),
+ (uni_name, Name.Namespace),
+ default('#pop'),
+ ],
+ 'rfstringescape': [
+ (r'\{\{', String.Escape),
+ (r'\}\}', String.Escape),
+ ],
+ 'fstringescape': [
+ include('rfstringescape'),
+ include('stringescape'),
+ ],
+ 'bytesescape': [
+ (r'\\([\\abfnrtv"\']|\n|x[a-fA-F0-9]{2}|[0-7]{1,3})', String.Escape)
+ ],
+ 'stringescape': [
+ (r'\\(N\{.*?\}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{8})', String.Escape),
+ include('bytesescape')
+ ],
+ 'fstrings-single': fstring_rules(String.Single),
+ 'fstrings-double': fstring_rules(String.Double),
+ 'strings-single': innerstring_rules(String.Single),
+ 'strings-double': innerstring_rules(String.Double),
+ 'dqf': [
+ (r'"', String.Double, '#pop'),
+ (r'\\\\|\\"|\\\n', String.Escape), # included here for raw strings
+ include('fstrings-double')
+ ],
+ 'sqf': [
+ (r"'", String.Single, '#pop'),
+ (r"\\\\|\\'|\\\n", String.Escape), # included here for raw strings
+ include('fstrings-single')
+ ],
+ 'dqs': [
+ (r'"', String.Double, '#pop'),
+ (r'\\\\|\\"|\\\n', String.Escape), # included here for raw strings
+ include('strings-double')
+ ],
+ 'sqs': [
+ (r"'", String.Single, '#pop'),
+ (r"\\\\|\\'|\\\n", String.Escape), # included here for raw strings
+ include('strings-single')
+ ],
+ 'tdqf': [
+ (r'"""', String.Double, '#pop'),
+ include('fstrings-double'),
+ (r'\n', String.Double)
+ ],
+ 'tsqf': [
+ (r"'''", String.Single, '#pop'),
+ include('fstrings-single'),
+ (r'\n', String.Single)
+ ],
+ 'tdqs': [
+ (r'"""', String.Double, '#pop'),
+ include('strings-double'),
+ (r'\n', String.Double)
+ ],
+ 'tsqs': [
+ (r"'''", String.Single, '#pop'),
+ include('strings-single'),
+ (r'\n', String.Single)
+ ],
+ }
+
+ def analyse_text(text):
+ return shebang_matches(text, r'pythonw?(3(\.\d)?)?') or \
+ 'import ' in text[:1000]
+
+
+Python3Lexer = PythonLexer
+
+
+class Python2Lexer(RegexLexer):
+ """
+ For Python 2.x source code.
+
+ .. versionchanged:: 2.5
+ This class has been renamed from ``PythonLexer``. ``PythonLexer`` now
+ refers to the Python 3 variant. File name patterns like ``*.py`` have
+ been moved to Python 3 as well.
+ """
+
+ name = 'Python 2.x'
+ url = 'http://www.python.org'
+ aliases = ['python2', 'py2']
+ filenames = [] # now taken over by PythonLexer (3.x)
+ mimetypes = ['text/x-python2', 'application/x-python2']
+
+ def innerstring_rules(ttype):
+ return [
+ # the old style '%s' % (...) string formatting
+ (r'%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?'
+ '[hlL]?[E-GXc-giorsux%]', String.Interpol),
+ # backslashes, quotes and formatting signs must be parsed one at a time
+ (r'[^\\\'"%\n]+', ttype),
+ (r'[\'"\\]', ttype),
+ # unhandled string formatting sign
+ (r'%', ttype),
+ # newlines are an error (use "nl" state)
+ ]
+
+ tokens = {
+ 'root': [
+ (r'\n', Text),
+ (r'^(\s*)([rRuUbB]{,2})("""(?:.|\n)*?""")',
+ bygroups(Text, String.Affix, String.Doc)),
+ (r"^(\s*)([rRuUbB]{,2})('''(?:.|\n)*?''')",
+ bygroups(Text, String.Affix, String.Doc)),
+ (r'[^\S\n]+', Text),
+ (r'\A#!.+$', Comment.Hashbang),
+ (r'#.*$', Comment.Single),
+ (r'[]{}:(),;[]', Punctuation),
+ (r'\\\n', Text),
+ (r'\\', Text),
+ (r'(in|is|and|or|not)\b', Operator.Word),
+ (r'!=|==|<<|>>|[-~+/*%=<>&^|.]', Operator),
+ include('keywords'),
+ (r'(def)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'funcname'),
+ (r'(class)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'classname'),
+ (r'(from)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text),
+ 'fromimport'),
+ (r'(import)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text),
+ 'import'),
+ include('builtins'),
+ include('magicfuncs'),
+ include('magicvars'),
+ include('backtick'),
+ ('([rR]|[uUbB][rR]|[rR][uUbB])(""")',
+ bygroups(String.Affix, String.Double), 'tdqs'),
+ ("([rR]|[uUbB][rR]|[rR][uUbB])(''')",
+ bygroups(String.Affix, String.Single), 'tsqs'),
+ ('([rR]|[uUbB][rR]|[rR][uUbB])(")',
+ bygroups(String.Affix, String.Double), 'dqs'),
+ ("([rR]|[uUbB][rR]|[rR][uUbB])(')",
+ bygroups(String.Affix, String.Single), 'sqs'),
+ ('([uUbB]?)(""")', bygroups(String.Affix, String.Double),
+ combined('stringescape', 'tdqs')),
+ ("([uUbB]?)(''')", bygroups(String.Affix, String.Single),
+ combined('stringescape', 'tsqs')),
+ ('([uUbB]?)(")', bygroups(String.Affix, String.Double),
+ combined('stringescape', 'dqs')),
+ ("([uUbB]?)(')", bygroups(String.Affix, String.Single),
+ combined('stringescape', 'sqs')),
+ include('name'),
+ include('numbers'),
+ ],
+ 'keywords': [
+ (words((
+ 'assert', 'break', 'continue', 'del', 'elif', 'else', 'except',
+ 'exec', 'finally', 'for', 'global', 'if', 'lambda', 'pass',
+ 'print', 'raise', 'return', 'try', 'while', 'yield',
+ 'yield from', 'as', 'with'), suffix=r'\b'),
+ Keyword),
+ ],
+ 'builtins': [
+ (words((
+ '__import__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin',
+ 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod',
+ 'cmp', 'coerce', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod',
+ 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float',
+ 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'hex', 'id',
+ 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len',
+ 'list', 'locals', 'long', 'map', 'max', 'min', 'next', 'object',
+ 'oct', 'open', 'ord', 'pow', 'property', 'range', 'raw_input', 'reduce',
+ 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
+ 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type',
+ 'unichr', 'unicode', 'vars', 'xrange', 'zip'),
+ prefix=r'(?<!\.)', suffix=r'\b'),
+ Name.Builtin),
+ (r'(?<!\.)(self|None|Ellipsis|NotImplemented|False|True|cls'
+ r')\b', Name.Builtin.Pseudo),
+ (words((
+ 'ArithmeticError', 'AssertionError', 'AttributeError',
+ 'BaseException', 'DeprecationWarning', 'EOFError', 'EnvironmentError',
+ 'Exception', 'FloatingPointError', 'FutureWarning', 'GeneratorExit',
+ 'IOError', 'ImportError', 'ImportWarning', 'IndentationError',
+ 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
+ 'MemoryError', 'NameError',
+ 'NotImplementedError', 'OSError', 'OverflowError', 'OverflowWarning',
+ 'PendingDeprecationWarning', 'ReferenceError',
+ 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration',
+ 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit',
+ 'TabError', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',
+ 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError',
+ 'UnicodeWarning', 'UserWarning', 'ValueError', 'VMSError', 'Warning',
+ 'WindowsError', 'ZeroDivisionError'), prefix=r'(?<!\.)', suffix=r'\b'),
+ Name.Exception),
+ ],
+ 'magicfuncs': [
+ (words((
+ '__abs__', '__add__', '__and__', '__call__', '__cmp__', '__coerce__',
+ '__complex__', '__contains__', '__del__', '__delattr__', '__delete__',
+ '__delitem__', '__delslice__', '__div__', '__divmod__', '__enter__',
+ '__eq__', '__exit__', '__float__', '__floordiv__', '__ge__', '__get__',
+ '__getattr__', '__getattribute__', '__getitem__', '__getslice__', '__gt__',
+ '__hash__', '__hex__', '__iadd__', '__iand__', '__idiv__', '__ifloordiv__',
+ '__ilshift__', '__imod__', '__imul__', '__index__', '__init__',
+ '__instancecheck__', '__int__', '__invert__', '__iop__', '__ior__',
+ '__ipow__', '__irshift__', '__isub__', '__iter__', '__itruediv__',
+ '__ixor__', '__le__', '__len__', '__long__', '__lshift__', '__lt__',
+ '__missing__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__',
+ '__nonzero__', '__oct__', '__op__', '__or__', '__pos__', '__pow__',
+ '__radd__', '__rand__', '__rcmp__', '__rdiv__', '__rdivmod__', '__repr__',
+ '__reversed__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__',
+ '__rop__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__',
+ '__rtruediv__', '__rxor__', '__set__', '__setattr__', '__setitem__',
+ '__setslice__', '__str__', '__sub__', '__subclasscheck__', '__truediv__',
+ '__unicode__', '__xor__'), suffix=r'\b'),
+ Name.Function.Magic),
+ ],
+ 'magicvars': [
+ (words((
+ '__bases__', '__class__', '__closure__', '__code__', '__defaults__',
+ '__dict__', '__doc__', '__file__', '__func__', '__globals__',
+ '__metaclass__', '__module__', '__mro__', '__name__', '__self__',
+ '__slots__', '__weakref__'),
+ suffix=r'\b'),
+ Name.Variable.Magic),
+ ],
+ 'numbers': [
+ (r'(\d+\.\d*|\d*\.\d+)([eE][+-]?[0-9]+)?j?', Number.Float),
+ (r'\d+[eE][+-]?[0-9]+j?', Number.Float),
+ (r'0[0-7]+j?', Number.Oct),
+ (r'0[bB][01]+', Number.Bin),
+ (r'0[xX][a-fA-F0-9]+', Number.Hex),
+ (r'\d+L', Number.Integer.Long),
+ (r'\d+j?', Number.Integer)
+ ],
+ 'backtick': [
+ ('`.*?`', String.Backtick),
+ ],
+ 'name': [
+ (r'@[\w.]+', Name.Decorator),
+ (r'[a-zA-Z_]\w*', Name),
+ ],
+ 'funcname': [
+ include('magicfuncs'),
+ (r'[a-zA-Z_]\w*', Name.Function, '#pop'),
+ default('#pop'),
+ ],
+ 'classname': [
+ (r'[a-zA-Z_]\w*', Name.Class, '#pop')
+ ],
+ 'import': [
+ (r'(?:[ \t]|\\\n)+', Text),
+ (r'as\b', Keyword.Namespace),
+ (r',', Operator),
+ (r'[a-zA-Z_][\w.]*', Name.Namespace),
+ default('#pop') # all else: go back
+ ],
+ 'fromimport': [
+ (r'(?:[ \t]|\\\n)+', Text),
+ (r'import\b', Keyword.Namespace, '#pop'),
+ # if None occurs here, it's "raise x from None", since None can
+ # never be a module name
+ (r'None\b', Name.Builtin.Pseudo, '#pop'),
+ # sadly, in "raise x from y" y will be highlighted as namespace too
+ (r'[a-zA-Z_.][\w.]*', Name.Namespace),
+ # anything else here also means "raise x from y" and is therefore
+ # not an error
+ default('#pop'),
+ ],
+ 'stringescape': [
+ (r'\\([\\abfnrtv"\']|\n|N\{.*?\}|u[a-fA-F0-9]{4}|'
+ r'U[a-fA-F0-9]{8}|x[a-fA-F0-9]{2}|[0-7]{1,3})', String.Escape)
+ ],
+ 'strings-single': innerstring_rules(String.Single),
+ 'strings-double': innerstring_rules(String.Double),
+ 'dqs': [
+ (r'"', String.Double, '#pop'),
+ (r'\\\\|\\"|\\\n', String.Escape), # included here for raw strings
+ include('strings-double')
+ ],
+ 'sqs': [
+ (r"'", String.Single, '#pop'),
+ (r"\\\\|\\'|\\\n", String.Escape), # included here for raw strings
+ include('strings-single')
+ ],
+ 'tdqs': [
+ (r'"""', String.Double, '#pop'),
+ include('strings-double'),
+ (r'\n', String.Double)
+ ],
+ 'tsqs': [
+ (r"'''", String.Single, '#pop'),
+ include('strings-single'),
+ (r'\n', String.Single)
+ ],
+ }
+
+ def analyse_text(text):
+ return shebang_matches(text, r'pythonw?2(\.\d)?')
+
+
+class PythonConsoleLexer(Lexer):
+ """
+ For Python console output or doctests, such as:
+
+ .. sourcecode:: pycon
+
+ >>> a = 'foo'
+ >>> print a
+ foo
+ >>> 1 / 0
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ ZeroDivisionError: integer division or modulo by zero
+
+ Additional options:
+
+ `python3`
+ Use Python 3 lexer for code. Default is ``True``.
+
+ .. versionadded:: 1.0
+ .. versionchanged:: 2.5
+ Now defaults to ``True``.
+ """
+ name = 'Python console session'
+ aliases = ['pycon']
+ mimetypes = ['text/x-python-doctest']
+
+ def __init__(self, **options):
+ self.python3 = get_bool_opt(options, 'python3', True)
+ Lexer.__init__(self, **options)
+
+ def get_tokens_unprocessed(self, text):
+ if self.python3:
+ pylexer = PythonLexer(**self.options)
+ tblexer = PythonTracebackLexer(**self.options)
+ else:
+ pylexer = Python2Lexer(**self.options)
+ tblexer = Python2TracebackLexer(**self.options)
+
+ curcode = ''
+ insertions = []
+ curtb = ''
+ tbindex = 0
+ tb = 0
+ for match in line_re.finditer(text):
+ line = match.group()
+ if line.startswith('>>> ') or line.startswith('... '):
+ tb = 0
+ insertions.append((len(curcode),
+ [(0, Generic.Prompt, line[:4])]))
+ curcode += line[4:]
+ elif line.rstrip() == '...' and not tb:
+ # only a new >>> prompt can end an exception block
+ # otherwise an ellipsis in place of the traceback frames
+ # will be mishandled
+ insertions.append((len(curcode),
+ [(0, Generic.Prompt, '...')]))
+ curcode += line[3:]
+ else:
+ if curcode:
+ yield from do_insertions(
+ insertions, pylexer.get_tokens_unprocessed(curcode))
+ curcode = ''
+ insertions = []
+ if (line.startswith('Traceback (most recent call last):') or
+ re.match(' File "[^"]+", line \\d+\\n$', line)):
+ tb = 1
+ curtb = line
+ tbindex = match.start()
+ elif line == 'KeyboardInterrupt\n':
+ yield match.start(), Name.Class, line
+ elif tb:
+ curtb += line
+ if not (line.startswith(' ') or line.strip() == '...'):
+ tb = 0
+ for i, t, v in tblexer.get_tokens_unprocessed(curtb):
+ yield tbindex+i, t, v
+ curtb = ''
+ else:
+ yield match.start(), Generic.Output, line
+ if curcode:
+ yield from do_insertions(insertions,
+ pylexer.get_tokens_unprocessed(curcode))
+ if curtb:
+ for i, t, v in tblexer.get_tokens_unprocessed(curtb):
+ yield tbindex+i, t, v
+
+
+class PythonTracebackLexer(RegexLexer):
+ """
+ For Python 3.x tracebacks, with support for chained exceptions.
+
+ .. versionadded:: 1.0
+
+ .. versionchanged:: 2.5
+ This is now the default ``PythonTracebackLexer``. It is still available
+ as the alias ``Python3TracebackLexer``.
+ """
+
+ name = 'Python Traceback'
+ aliases = ['pytb', 'py3tb']
+ filenames = ['*.pytb', '*.py3tb']
+ mimetypes = ['text/x-python-traceback', 'text/x-python3-traceback']
+
+ tokens = {
+ 'root': [
+ (r'\n', Text),
+ (r'^Traceback \(most recent call last\):\n', Generic.Traceback, 'intb'),
+ (r'^During handling of the above exception, another '
+ r'exception occurred:\n\n', Generic.Traceback),
+ (r'^The above exception was the direct cause of the '
+ r'following exception:\n\n', Generic.Traceback),
+ (r'^(?= File "[^"]+", line \d+)', Generic.Traceback, 'intb'),
+ (r'^.*\n', Other),
+ ],
+ 'intb': [
+ (r'^( File )("[^"]+")(, line )(\d+)(, in )(.+)(\n)',
+ bygroups(Text, Name.Builtin, Text, Number, Text, Name, Text)),
+ (r'^( File )("[^"]+")(, line )(\d+)(\n)',
+ bygroups(Text, Name.Builtin, Text, Number, Text)),
+ (r'^( )(.+)(\n)',
+ bygroups(Text, using(PythonLexer), Text), 'markers'),
+ (r'^([ \t]*)(\.\.\.)(\n)',
+ bygroups(Text, Comment, Text)), # for doctests...
+ (r'^([^:]+)(: )(.+)(\n)',
+ bygroups(Generic.Error, Text, Name, Text), '#pop'),
+ (r'^([a-zA-Z_][\w.]*)(:?\n)',
+ bygroups(Generic.Error, Text), '#pop')
+ ],
+ 'markers': [
+ # Either `PEP 657 <https://www.python.org/dev/peps/pep-0657/>`
+ # error locations in Python 3.11+, or single-caret markers
+ # for syntax errors before that.
+ (r'^( {4,})([~^]+)(\n)',
+ bygroups(Text, Punctuation.Marker, Text),
+ '#pop'),
+ default('#pop'),
+ ],
+ }
+
+
+Python3TracebackLexer = PythonTracebackLexer
+
+
+class Python2TracebackLexer(RegexLexer):
+ """
+ For Python tracebacks.
+
+ .. versionadded:: 0.7
+
+ .. versionchanged:: 2.5
+ This class has been renamed from ``PythonTracebackLexer``.
+ ``PythonTracebackLexer`` now refers to the Python 3 variant.
+ """
+
+ name = 'Python 2.x Traceback'
+ aliases = ['py2tb']
+ filenames = ['*.py2tb']
+ mimetypes = ['text/x-python2-traceback']
+
+ tokens = {
+ 'root': [
+ # Cover both (most recent call last) and (innermost last)
+ # The optional ^C allows us to catch keyboard interrupt signals.
+ (r'^(\^C)?(Traceback.*\n)',
+ bygroups(Text, Generic.Traceback), 'intb'),
+ # SyntaxError starts with this.
+ (r'^(?= File "[^"]+", line \d+)', Generic.Traceback, 'intb'),
+ (r'^.*\n', Other),
+ ],
+ 'intb': [
+ (r'^( File )("[^"]+")(, line )(\d+)(, in )(.+)(\n)',
+ bygroups(Text, Name.Builtin, Text, Number, Text, Name, Text)),
+ (r'^( File )("[^"]+")(, line )(\d+)(\n)',
+ bygroups(Text, Name.Builtin, Text, Number, Text)),
+ (r'^( )(.+)(\n)',
+ bygroups(Text, using(Python2Lexer), Text), 'marker'),
+ (r'^([ \t]*)(\.\.\.)(\n)',
+ bygroups(Text, Comment, Text)), # for doctests...
+ (r'^([^:]+)(: )(.+)(\n)',
+ bygroups(Generic.Error, Text, Name, Text), '#pop'),
+ (r'^([a-zA-Z_]\w*)(:?\n)',
+ bygroups(Generic.Error, Text), '#pop')
+ ],
+ 'marker': [
+ # For syntax errors.
+ (r'( {4,})(\^)', bygroups(Text, Punctuation.Marker), '#pop'),
+ default('#pop'),
+ ],
+ }
+
+
+class CythonLexer(RegexLexer):
+ """
+ For Pyrex and Cython source code.
+
+ .. versionadded:: 1.1
+ """
+
+ name = 'Cython'
+ url = 'http://cython.org'
+ aliases = ['cython', 'pyx', 'pyrex']
+ filenames = ['*.pyx', '*.pxd', '*.pxi']
+ mimetypes = ['text/x-cython', 'application/x-cython']
+
+ tokens = {
+ 'root': [
+ (r'\n', Text),
+ (r'^(\s*)("""(?:.|\n)*?""")', bygroups(Text, String.Doc)),
+ (r"^(\s*)('''(?:.|\n)*?''')", bygroups(Text, String.Doc)),
+ (r'[^\S\n]+', Text),
+ (r'#.*$', Comment),
+ (r'[]{}:(),;[]', Punctuation),
+ (r'\\\n', Text),
+ (r'\\', Text),
+ (r'(in|is|and|or|not)\b', Operator.Word),
+ (r'(<)([a-zA-Z0-9.?]+)(>)',
+ bygroups(Punctuation, Keyword.Type, Punctuation)),
+ (r'!=|==|<<|>>|[-~+/*%=<>&^|.?]', Operator),
+ (r'(from)(\d+)(<=)(\s+)(<)(\d+)(:)',
+ bygroups(Keyword, Number.Integer, Operator, Name, Operator,
+ Name, Punctuation)),
+ include('keywords'),
+ (r'(def|property)(\s+)', bygroups(Keyword, Text), 'funcname'),
+ (r'(cp?def)(\s+)', bygroups(Keyword, Text), 'cdef'),
+ # (should actually start a block with only cdefs)
+ (r'(cdef)(:)', bygroups(Keyword, Punctuation)),
+ (r'(class|struct)(\s+)', bygroups(Keyword, Text), 'classname'),
+ (r'(from)(\s+)', bygroups(Keyword, Text), 'fromimport'),
+ (r'(c?import)(\s+)', bygroups(Keyword, Text), 'import'),
+ include('builtins'),
+ include('backtick'),
+ ('(?:[rR]|[uU][rR]|[rR][uU])"""', String, 'tdqs'),
+ ("(?:[rR]|[uU][rR]|[rR][uU])'''", String, 'tsqs'),
+ ('(?:[rR]|[uU][rR]|[rR][uU])"', String, 'dqs'),
+ ("(?:[rR]|[uU][rR]|[rR][uU])'", String, 'sqs'),
+ ('[uU]?"""', String, combined('stringescape', 'tdqs')),
+ ("[uU]?'''", String, combined('stringescape', 'tsqs')),
+ ('[uU]?"', String, combined('stringescape', 'dqs')),
+ ("[uU]?'", String, combined('stringescape', 'sqs')),
+ include('name'),
+ include('numbers'),
+ ],
+ 'keywords': [
+ (words((
+ 'assert', 'async', 'await', 'break', 'by', 'continue', 'ctypedef', 'del', 'elif',
+ 'else', 'except', 'except?', 'exec', 'finally', 'for', 'fused', 'gil',
+ 'global', 'if', 'include', 'lambda', 'nogil', 'pass', 'print',
+ 'raise', 'return', 'try', 'while', 'yield', 'as', 'with'), suffix=r'\b'),
+ Keyword),
+ (r'(DEF|IF|ELIF|ELSE)\b', Comment.Preproc),
+ ],
+ 'builtins': [
+ (words((
+ '__import__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bint',
+ 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr',
+ 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'delattr',
+ 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit',
+ 'file', 'filter', 'float', 'frozenset', 'getattr', 'globals',
+ 'hasattr', 'hash', 'hex', 'id', 'input', 'int', 'intern', 'isinstance',
+ 'issubclass', 'iter', 'len', 'list', 'locals', 'long', 'map', 'max',
+ 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'property', 'Py_ssize_t',
+ 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed',
+ 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod',
+ 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'unsigned',
+ 'vars', 'xrange', 'zip'), prefix=r'(?<!\.)', suffix=r'\b'),
+ Name.Builtin),
+ (r'(?<!\.)(self|None|Ellipsis|NotImplemented|False|True|NULL'
+ r')\b', Name.Builtin.Pseudo),
+ (words((
+ 'ArithmeticError', 'AssertionError', 'AttributeError',
+ 'BaseException', 'DeprecationWarning', 'EOFError', 'EnvironmentError',
+ 'Exception', 'FloatingPointError', 'FutureWarning', 'GeneratorExit',
+ 'IOError', 'ImportError', 'ImportWarning', 'IndentationError',
+ 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
+ 'MemoryError', 'NameError', 'NotImplemented', 'NotImplementedError',
+ 'OSError', 'OverflowError', 'OverflowWarning',
+ 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError',
+ 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError',
+ 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError',
+ 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',
+ 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError',
+ 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning',
+ 'ZeroDivisionError'), prefix=r'(?<!\.)', suffix=r'\b'),
+ Name.Exception),
+ ],
+ 'numbers': [
+ (r'(\d+\.?\d*|\d*\.\d+)([eE][+-]?[0-9]+)?', Number.Float),
+ (r'0\d+', Number.Oct),
+ (r'0[xX][a-fA-F0-9]+', Number.Hex),
+ (r'\d+L', Number.Integer.Long),
+ (r'\d+', Number.Integer)
+ ],
+ 'backtick': [
+ ('`.*?`', String.Backtick),
+ ],
+ 'name': [
+ (r'@\w+', Name.Decorator),
+ (r'[a-zA-Z_]\w*', Name),
+ ],
+ 'funcname': [
+ (r'[a-zA-Z_]\w*', Name.Function, '#pop')
+ ],
+ 'cdef': [
+ (r'(public|readonly|extern|api|inline)\b', Keyword.Reserved),
+ (r'(struct|enum|union|class)\b', Keyword),
+ (r'([a-zA-Z_]\w*)(\s*)(?=[(:#=]|$)',
+ bygroups(Name.Function, Text), '#pop'),
+ (r'([a-zA-Z_]\w*)(\s*)(,)',
+ bygroups(Name.Function, Text, Punctuation)),
+ (r'from\b', Keyword, '#pop'),
+ (r'as\b', Keyword),
+ (r':', Punctuation, '#pop'),
+ (r'(?=["\'])', Text, '#pop'),
+ (r'[a-zA-Z_]\w*', Keyword.Type),
+ (r'.', Text),
+ ],
+ 'classname': [
+ (r'[a-zA-Z_]\w*', Name.Class, '#pop')
+ ],
+ 'import': [
+ (r'(\s+)(as)(\s+)', bygroups(Text, Keyword, Text)),
+ (r'[a-zA-Z_][\w.]*', Name.Namespace),
+ (r'(\s*)(,)(\s*)', bygroups(Text, Operator, Text)),
+ default('#pop') # all else: go back
+ ],
+ 'fromimport': [
+ (r'(\s+)(c?import)\b', bygroups(Text, Keyword), '#pop'),
+ (r'[a-zA-Z_.][\w.]*', Name.Namespace),
+ # ``cdef foo from "header"``, or ``for foo from 0 < i < 10``
+ default('#pop'),
+ ],
+ 'stringescape': [
+ (r'\\([\\abfnrtv"\']|\n|N\{.*?\}|u[a-fA-F0-9]{4}|'
+ r'U[a-fA-F0-9]{8}|x[a-fA-F0-9]{2}|[0-7]{1,3})', String.Escape)
+ ],
+ 'strings': [
+ (r'%(\([a-zA-Z0-9]+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?'
+ '[hlL]?[E-GXc-giorsux%]', String.Interpol),
+ (r'[^\\\'"%\n]+', String),
+ # quotes, percents and backslashes must be parsed one at a time
+ (r'[\'"\\]', String),
+ # unhandled string formatting sign
+ (r'%', String)
+ # newlines are an error (use "nl" state)
+ ],
+ 'nl': [
+ (r'\n', String)
+ ],
+ 'dqs': [
+ (r'"', String, '#pop'),
+ (r'\\\\|\\"|\\\n', String.Escape), # included here again for raw strings
+ include('strings')
+ ],
+ 'sqs': [
+ (r"'", String, '#pop'),
+ (r"\\\\|\\'|\\\n", String.Escape), # included here again for raw strings
+ include('strings')
+ ],
+ 'tdqs': [
+ (r'"""', String, '#pop'),
+ include('strings'),
+ include('nl')
+ ],
+ 'tsqs': [
+ (r"'''", String, '#pop'),
+ include('strings'),
+ include('nl')
+ ],
+ }
+
+
+class DgLexer(RegexLexer):
+ """
+ Lexer for dg,
+ a functional and object-oriented programming language
+ running on the CPython 3 VM.
+
+ .. versionadded:: 1.6
+ """
+ name = 'dg'
+ aliases = ['dg']
+ filenames = ['*.dg']
+ mimetypes = ['text/x-dg']
+
+ tokens = {
+ 'root': [
+ (r'\s+', Text),
+ (r'#.*?$', Comment.Single),
+
+ (r'(?i)0b[01]+', Number.Bin),
+ (r'(?i)0o[0-7]+', Number.Oct),
+ (r'(?i)0x[0-9a-f]+', Number.Hex),
+ (r'(?i)[+-]?[0-9]+\.[0-9]+(e[+-]?[0-9]+)?j?', Number.Float),
+ (r'(?i)[+-]?[0-9]+e[+-]?\d+j?', Number.Float),
+ (r'(?i)[+-]?[0-9]+j?', Number.Integer),
+
+ (r"(?i)(br|r?b?)'''", String, combined('stringescape', 'tsqs', 'string')),
+ (r'(?i)(br|r?b?)"""', String, combined('stringescape', 'tdqs', 'string')),
+ (r"(?i)(br|r?b?)'", String, combined('stringescape', 'sqs', 'string')),
+ (r'(?i)(br|r?b?)"', String, combined('stringescape', 'dqs', 'string')),
+
+ (r"`\w+'*`", Operator),
+ (r'\b(and|in|is|or|where)\b', Operator.Word),
+ (r'[!$%&*+\-./:<-@\\^|~;,]+', Operator),
+
+ (words((
+ 'bool', 'bytearray', 'bytes', 'classmethod', 'complex', 'dict', 'dict\'',
+ 'float', 'frozenset', 'int', 'list', 'list\'', 'memoryview', 'object',
+ 'property', 'range', 'set', 'set\'', 'slice', 'staticmethod', 'str',
+ 'super', 'tuple', 'tuple\'', 'type'),
+ prefix=r'(?<!\.)', suffix=r'(?![\'\w])'),
+ Name.Builtin),
+ (words((
+ '__import__', 'abs', 'all', 'any', 'bin', 'bind', 'chr', 'cmp', 'compile',
+ 'complex', 'delattr', 'dir', 'divmod', 'drop', 'dropwhile', 'enumerate',
+ 'eval', 'exhaust', 'filter', 'flip', 'foldl1?', 'format', 'fst',
+ 'getattr', 'globals', 'hasattr', 'hash', 'head', 'hex', 'id', 'init',
+ 'input', 'isinstance', 'issubclass', 'iter', 'iterate', 'last', 'len',
+ 'locals', 'map', 'max', 'min', 'next', 'oct', 'open', 'ord', 'pow',
+ 'print', 'repr', 'reversed', 'round', 'setattr', 'scanl1?', 'snd',
+ 'sorted', 'sum', 'tail', 'take', 'takewhile', 'vars', 'zip'),
+ prefix=r'(?<!\.)', suffix=r'(?![\'\w])'),
+ Name.Builtin),
+ (r"(?<!\.)(self|Ellipsis|NotImplemented|None|True|False)(?!['\w])",
+ Name.Builtin.Pseudo),
+
+ (r"(?<!\.)[A-Z]\w*(Error|Exception|Warning)'*(?!['\w])",
+ Name.Exception),
+ (r"(?<!\.)(Exception|GeneratorExit|KeyboardInterrupt|StopIteration|"
+ r"SystemExit)(?!['\w])", Name.Exception),
+
+ (r"(?<![\w.])(except|finally|for|if|import|not|otherwise|raise|"
+ r"subclass|while|with|yield)(?!['\w])", Keyword.Reserved),
+
+ (r"[A-Z_]+'*(?!['\w])", Name),
+ (r"[A-Z]\w+'*(?!['\w])", Keyword.Type),
+ (r"\w+'*", Name),
+
+ (r'[()]', Punctuation),
+ (r'.', Error),
+ ],
+ 'stringescape': [
+ (r'\\([\\abfnrtv"\']|\n|N\{.*?\}|u[a-fA-F0-9]{4}|'
+ r'U[a-fA-F0-9]{8}|x[a-fA-F0-9]{2}|[0-7]{1,3})', String.Escape)
+ ],
+ 'string': [
+ (r'%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?'
+ '[hlL]?[E-GXc-giorsux%]', String.Interpol),
+ (r'[^\\\'"%\n]+', String),
+ # quotes, percents and backslashes must be parsed one at a time
+ (r'[\'"\\]', String),
+ # unhandled string formatting sign
+ (r'%', String),
+ (r'\n', String)
+ ],
+ 'dqs': [
+ (r'"', String, '#pop')
+ ],
+ 'sqs': [
+ (r"'", String, '#pop')
+ ],
+ 'tdqs': [
+ (r'"""', String, '#pop')
+ ],
+ 'tsqs': [
+ (r"'''", String, '#pop')
+ ],
+ }
+
+
+class NumPyLexer(PythonLexer):
+ """
+ A Python lexer recognizing Numerical Python builtins.
+
+ .. versionadded:: 0.10
+ """
+
+ name = 'NumPy'
+ url = 'https://numpy.org/'
+ aliases = ['numpy']
+
+ # override the mimetypes to not inherit them from python
+ mimetypes = []
+ filenames = []
+
+ EXTRA_KEYWORDS = {
+ 'abs', 'absolute', 'accumulate', 'add', 'alen', 'all', 'allclose',
+ 'alltrue', 'alterdot', 'amax', 'amin', 'angle', 'any', 'append',
+ 'apply_along_axis', 'apply_over_axes', 'arange', 'arccos', 'arccosh',
+ 'arcsin', 'arcsinh', 'arctan', 'arctan2', 'arctanh', 'argmax', 'argmin',
+ 'argsort', 'argwhere', 'around', 'array', 'array2string', 'array_equal',
+ 'array_equiv', 'array_repr', 'array_split', 'array_str', 'arrayrange',
+ 'asanyarray', 'asarray', 'asarray_chkfinite', 'ascontiguousarray',
+ 'asfarray', 'asfortranarray', 'asmatrix', 'asscalar', 'astype',
+ 'atleast_1d', 'atleast_2d', 'atleast_3d', 'average', 'bartlett',
+ 'base_repr', 'beta', 'binary_repr', 'bincount', 'binomial',
+ 'bitwise_and', 'bitwise_not', 'bitwise_or', 'bitwise_xor', 'blackman',
+ 'bmat', 'broadcast', 'byte_bounds', 'bytes', 'byteswap', 'c_',
+ 'can_cast', 'ceil', 'choose', 'clip', 'column_stack', 'common_type',
+ 'compare_chararrays', 'compress', 'concatenate', 'conj', 'conjugate',
+ 'convolve', 'copy', 'corrcoef', 'correlate', 'cos', 'cosh', 'cov',
+ 'cross', 'cumprod', 'cumproduct', 'cumsum', 'delete', 'deprecate',
+ 'diag', 'diagflat', 'diagonal', 'diff', 'digitize', 'disp', 'divide',
+ 'dot', 'dsplit', 'dstack', 'dtype', 'dump', 'dumps', 'ediff1d', 'empty',
+ 'empty_like', 'equal', 'exp', 'expand_dims', 'expm1', 'extract', 'eye',
+ 'fabs', 'fastCopyAndTranspose', 'fft', 'fftfreq', 'fftshift', 'fill',
+ 'finfo', 'fix', 'flat', 'flatnonzero', 'flatten', 'fliplr', 'flipud',
+ 'floor', 'floor_divide', 'fmod', 'frexp', 'fromarrays', 'frombuffer',
+ 'fromfile', 'fromfunction', 'fromiter', 'frompyfunc', 'fromstring',
+ 'generic', 'get_array_wrap', 'get_include', 'get_numarray_include',
+ 'get_numpy_include', 'get_printoptions', 'getbuffer', 'getbufsize',
+ 'geterr', 'geterrcall', 'geterrobj', 'getfield', 'gradient', 'greater',
+ 'greater_equal', 'gumbel', 'hamming', 'hanning', 'histogram',
+ 'histogram2d', 'histogramdd', 'hsplit', 'hstack', 'hypot', 'i0',
+ 'identity', 'ifft', 'imag', 'index_exp', 'indices', 'inf', 'info',
+ 'inner', 'insert', 'int_asbuffer', 'interp', 'intersect1d',
+ 'intersect1d_nu', 'inv', 'invert', 'iscomplex', 'iscomplexobj',
+ 'isfinite', 'isfortran', 'isinf', 'isnan', 'isneginf', 'isposinf',
+ 'isreal', 'isrealobj', 'isscalar', 'issctype', 'issubclass_',
+ 'issubdtype', 'issubsctype', 'item', 'itemset', 'iterable', 'ix_',
+ 'kaiser', 'kron', 'ldexp', 'left_shift', 'less', 'less_equal', 'lexsort',
+ 'linspace', 'load', 'loads', 'loadtxt', 'log', 'log10', 'log1p', 'log2',
+ 'logical_and', 'logical_not', 'logical_or', 'logical_xor', 'logspace',
+ 'lstsq', 'mat', 'matrix', 'max', 'maximum', 'maximum_sctype',
+ 'may_share_memory', 'mean', 'median', 'meshgrid', 'mgrid', 'min',
+ 'minimum', 'mintypecode', 'mod', 'modf', 'msort', 'multiply', 'nan',
+ 'nan_to_num', 'nanargmax', 'nanargmin', 'nanmax', 'nanmin', 'nansum',
+ 'ndenumerate', 'ndim', 'ndindex', 'negative', 'newaxis', 'newbuffer',
+ 'newbyteorder', 'nonzero', 'not_equal', 'obj2sctype', 'ogrid', 'ones',
+ 'ones_like', 'outer', 'permutation', 'piecewise', 'pinv', 'pkgload',
+ 'place', 'poisson', 'poly', 'poly1d', 'polyadd', 'polyder', 'polydiv',
+ 'polyfit', 'polyint', 'polymul', 'polysub', 'polyval', 'power', 'prod',
+ 'product', 'ptp', 'put', 'putmask', 'r_', 'randint', 'random_integers',
+ 'random_sample', 'ranf', 'rank', 'ravel', 'real', 'real_if_close',
+ 'recarray', 'reciprocal', 'reduce', 'remainder', 'repeat', 'require',
+ 'reshape', 'resize', 'restoredot', 'right_shift', 'rint', 'roll',
+ 'rollaxis', 'roots', 'rot90', 'round', 'round_', 'row_stack', 's_',
+ 'sample', 'savetxt', 'sctype2char', 'searchsorted', 'seed', 'select',
+ 'set_numeric_ops', 'set_printoptions', 'set_string_function',
+ 'setbufsize', 'setdiff1d', 'seterr', 'seterrcall', 'seterrobj',
+ 'setfield', 'setflags', 'setmember1d', 'setxor1d', 'shape',
+ 'show_config', 'shuffle', 'sign', 'signbit', 'sin', 'sinc', 'sinh',
+ 'size', 'slice', 'solve', 'sometrue', 'sort', 'sort_complex', 'source',
+ 'split', 'sqrt', 'square', 'squeeze', 'standard_normal', 'std',
+ 'subtract', 'sum', 'svd', 'swapaxes', 'take', 'tan', 'tanh', 'tensordot',
+ 'test', 'tile', 'tofile', 'tolist', 'tostring', 'trace', 'transpose',
+ 'trapz', 'tri', 'tril', 'trim_zeros', 'triu', 'true_divide', 'typeDict',
+ 'typename', 'uniform', 'union1d', 'unique', 'unique1d', 'unravel_index',
+ 'unwrap', 'vander', 'var', 'vdot', 'vectorize', 'view', 'vonmises',
+ 'vsplit', 'vstack', 'weibull', 'where', 'who', 'zeros', 'zeros_like'
+ }
+
+ def get_tokens_unprocessed(self, text):
+ for index, token, value in \
+ PythonLexer.get_tokens_unprocessed(self, text):
+ if token is Name and value in self.EXTRA_KEYWORDS:
+ yield index, Keyword.Pseudo, value
+ else:
+ yield index, token, value
+
+ def analyse_text(text):
+ ltext = text[:1000]
+ return (shebang_matches(text, r'pythonw?(3(\.\d)?)?') or
+ 'import ' in ltext) \
+ and ('import numpy' in ltext or 'from numpy import' in ltext)
diff --git a/src/pip/_vendor/pygments/modeline.py b/src/pip/_vendor/pygments/modeline.py
new file mode 100644
index 000000000..43630835c
--- /dev/null
+++ b/src/pip/_vendor/pygments/modeline.py
@@ -0,0 +1,43 @@
+"""
+ pygments.modeline
+ ~~~~~~~~~~~~~~~~~
+
+ A simple modeline parser (based on pymodeline).
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import re
+
+__all__ = ['get_filetype_from_buffer']
+
+
+modeline_re = re.compile(r'''
+ (?: vi | vim | ex ) (?: [<=>]? \d* )? :
+ .* (?: ft | filetype | syn | syntax ) = ( [^:\s]+ )
+''', re.VERBOSE)
+
+
+def get_filetype_from_line(l):
+ m = modeline_re.search(l)
+ if m:
+ return m.group(1)
+
+
+def get_filetype_from_buffer(buf, max_lines=5):
+ """
+ Scan the buffer for modelines and return filetype if one is found.
+ """
+ lines = buf.splitlines()
+ for l in lines[-1:-max_lines-1:-1]:
+ ret = get_filetype_from_line(l)
+ if ret:
+ return ret
+ for i in range(max_lines, -1, -1):
+ if i < len(lines):
+ ret = get_filetype_from_line(lines[i])
+ if ret:
+ return ret
+
+ return None
diff --git a/src/pip/_vendor/pygments/plugin.py b/src/pip/_vendor/pygments/plugin.py
new file mode 100644
index 000000000..3590bee8d
--- /dev/null
+++ b/src/pip/_vendor/pygments/plugin.py
@@ -0,0 +1,88 @@
+"""
+ pygments.plugin
+ ~~~~~~~~~~~~~~~
+
+ Pygments plugin interface. By default, this tries to use
+ ``importlib.metadata``, which is in the Python standard
+ library since Python 3.8, or its ``importlib_metadata``
+ backport for earlier versions of Python. It falls back on
+ ``pkg_resources`` if not found. Finally, if ``pkg_resources``
+ is not found either, no plugins are loaded at all.
+
+ lexer plugins::
+
+ [pygments.lexers]
+ yourlexer = yourmodule:YourLexer
+
+ formatter plugins::
+
+ [pygments.formatters]
+ yourformatter = yourformatter:YourFormatter
+ /.ext = yourformatter:YourFormatter
+
+ As you can see, you can define extensions for the formatter
+ with a leading slash.
+
+ syntax plugins::
+
+ [pygments.styles]
+ yourstyle = yourstyle:YourStyle
+
+ filter plugin::
+
+ [pygments.filter]
+ yourfilter = yourfilter:YourFilter
+
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+LEXER_ENTRY_POINT = 'pygments.lexers'
+FORMATTER_ENTRY_POINT = 'pygments.formatters'
+STYLE_ENTRY_POINT = 'pygments.styles'
+FILTER_ENTRY_POINT = 'pygments.filters'
+
+
+def iter_entry_points(group_name):
+ try:
+ from importlib.metadata import entry_points
+ except ImportError:
+ try:
+ from importlib_metadata import entry_points
+ except ImportError:
+ try:
+ from pip._vendor.pkg_resources import iter_entry_points
+ except (ImportError, OSError):
+ return []
+ else:
+ return iter_entry_points(group_name)
+ groups = entry_points()
+ if hasattr(groups, 'select'):
+ # New interface in Python 3.10 and newer versions of the
+ # importlib_metadata backport.
+ return groups.select(group=group_name)
+ else:
+ # Older interface, deprecated in Python 3.10 and recent
+ # importlib_metadata, but we need it in Python 3.8 and 3.9.
+ return groups.get(group_name, [])
+
+
+def find_plugin_lexers():
+ for entrypoint in iter_entry_points(LEXER_ENTRY_POINT):
+ yield entrypoint.load()
+
+
+def find_plugin_formatters():
+ for entrypoint in iter_entry_points(FORMATTER_ENTRY_POINT):
+ yield entrypoint.name, entrypoint.load()
+
+
+def find_plugin_styles():
+ for entrypoint in iter_entry_points(STYLE_ENTRY_POINT):
+ yield entrypoint.name, entrypoint.load()
+
+
+def find_plugin_filters():
+ for entrypoint in iter_entry_points(FILTER_ENTRY_POINT):
+ yield entrypoint.name, entrypoint.load()
diff --git a/src/pip/_vendor/pygments/regexopt.py b/src/pip/_vendor/pygments/regexopt.py
new file mode 100644
index 000000000..ae0079199
--- /dev/null
+++ b/src/pip/_vendor/pygments/regexopt.py
@@ -0,0 +1,91 @@
+"""
+ pygments.regexopt
+ ~~~~~~~~~~~~~~~~~
+
+ An algorithm that generates optimized regexes for matching long lists of
+ literal strings.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import re
+from re import escape
+from os.path import commonprefix
+from itertools import groupby
+from operator import itemgetter
+
+CS_ESCAPE = re.compile(r'[\[\^\\\-\]]')
+FIRST_ELEMENT = itemgetter(0)
+
+
+def make_charset(letters):
+ return '[' + CS_ESCAPE.sub(lambda m: '\\' + m.group(), ''.join(letters)) + ']'
+
+
+def regex_opt_inner(strings, open_paren):
+ """Return a regex that matches any string in the sorted list of strings."""
+ close_paren = open_paren and ')' or ''
+ # print strings, repr(open_paren)
+ if not strings:
+ # print '-> nothing left'
+ return ''
+ first = strings[0]
+ if len(strings) == 1:
+ # print '-> only 1 string'
+ return open_paren + escape(first) + close_paren
+ if not first:
+ # print '-> first string empty'
+ return open_paren + regex_opt_inner(strings[1:], '(?:') \
+ + '?' + close_paren
+ if len(first) == 1:
+ # multiple one-char strings? make a charset
+ oneletter = []
+ rest = []
+ for s in strings:
+ if len(s) == 1:
+ oneletter.append(s)
+ else:
+ rest.append(s)
+ if len(oneletter) > 1: # do we have more than one oneletter string?
+ if rest:
+ # print '-> 1-character + rest'
+ return open_paren + regex_opt_inner(rest, '') + '|' \
+ + make_charset(oneletter) + close_paren
+ # print '-> only 1-character'
+ return open_paren + make_charset(oneletter) + close_paren
+ prefix = commonprefix(strings)
+ if prefix:
+ plen = len(prefix)
+ # we have a prefix for all strings
+ # print '-> prefix:', prefix
+ return open_paren + escape(prefix) \
+ + regex_opt_inner([s[plen:] for s in strings], '(?:') \
+ + close_paren
+ # is there a suffix?
+ strings_rev = [s[::-1] for s in strings]
+ suffix = commonprefix(strings_rev)
+ if suffix:
+ slen = len(suffix)
+ # print '-> suffix:', suffix[::-1]
+ return open_paren \
+ + regex_opt_inner(sorted(s[:-slen] for s in strings), '(?:') \
+ + escape(suffix[::-1]) + close_paren
+ # recurse on common 1-string prefixes
+ # print '-> last resort'
+ return open_paren + \
+ '|'.join(regex_opt_inner(list(group[1]), '')
+ for group in groupby(strings, lambda s: s[0] == first[0])) \
+ + close_paren
+
+
+def regex_opt(strings, prefix='', suffix=''):
+ """Return a compiled regex that matches any string in the given list.
+
+ The strings to match must be literal strings, not regexes. They will be
+ regex-escaped.
+
+ *prefix* and *suffix* are pre- and appended to the final regex.
+ """
+ strings = sorted(strings)
+ return prefix + regex_opt_inner(strings, '(') + suffix
diff --git a/src/pip/_vendor/pygments/scanner.py b/src/pip/_vendor/pygments/scanner.py
new file mode 100644
index 000000000..d47ed4828
--- /dev/null
+++ b/src/pip/_vendor/pygments/scanner.py
@@ -0,0 +1,104 @@
+"""
+ pygments.scanner
+ ~~~~~~~~~~~~~~~~
+
+ This library implements a regex based scanner. Some languages
+ like Pascal are easy to parse but have some keywords that
+ depend on the context. Because of this it's impossible to lex
+ that just by using a regular expression lexer like the
+ `RegexLexer`.
+
+ Have a look at the `DelphiLexer` to get an idea of how to use
+ this scanner.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+import re
+
+
+class EndOfText(RuntimeError):
+ """
+ Raise if end of text is reached and the user
+ tried to call a match function.
+ """
+
+
+class Scanner:
+ """
+ Simple scanner
+
+ All method patterns are regular expression strings (not
+ compiled expressions!)
+ """
+
+ def __init__(self, text, flags=0):
+ """
+ :param text: The text which should be scanned
+ :param flags: default regular expression flags
+ """
+ self.data = text
+ self.data_length = len(text)
+ self.start_pos = 0
+ self.pos = 0
+ self.flags = flags
+ self.last = None
+ self.match = None
+ self._re_cache = {}
+
+ def eos(self):
+ """`True` if the scanner reached the end of text."""
+ return self.pos >= self.data_length
+ eos = property(eos, eos.__doc__)
+
+ def check(self, pattern):
+ """
+ Apply `pattern` on the current position and return
+ the match object. (Doesn't touch pos). Use this for
+ lookahead.
+ """
+ if self.eos:
+ raise EndOfText()
+ if pattern not in self._re_cache:
+ self._re_cache[pattern] = re.compile(pattern, self.flags)
+ return self._re_cache[pattern].match(self.data, self.pos)
+
+ def test(self, pattern):
+ """Apply a pattern on the current position and check
+ if it patches. Doesn't touch pos.
+ """
+ return self.check(pattern) is not None
+
+ def scan(self, pattern):
+ """
+ Scan the text for the given pattern and update pos/match
+ and related fields. The return value is a boolean that
+ indicates if the pattern matched. The matched value is
+ stored on the instance as ``match``, the last value is
+ stored as ``last``. ``start_pos`` is the position of the
+ pointer before the pattern was matched, ``pos`` is the
+ end position.
+ """
+ if self.eos:
+ raise EndOfText()
+ if pattern not in self._re_cache:
+ self._re_cache[pattern] = re.compile(pattern, self.flags)
+ self.last = self.match
+ m = self._re_cache[pattern].match(self.data, self.pos)
+ if m is None:
+ return False
+ self.start_pos = m.start()
+ self.pos = m.end()
+ self.match = m.group()
+ return True
+
+ def get_char(self):
+ """Scan exactly one char."""
+ self.scan('.')
+
+ def __repr__(self):
+ return '<%s %d/%d>' % (
+ self.__class__.__name__,
+ self.pos,
+ self.data_length
+ )
diff --git a/src/pip/_vendor/pygments/sphinxext.py b/src/pip/_vendor/pygments/sphinxext.py
new file mode 100644
index 000000000..c41bd49dd
--- /dev/null
+++ b/src/pip/_vendor/pygments/sphinxext.py
@@ -0,0 +1,155 @@
+"""
+ pygments.sphinxext
+ ~~~~~~~~~~~~~~~~~~
+
+ Sphinx extension to generate automatic documentation of lexers,
+ formatters and filters.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import sys
+
+from docutils import nodes
+from docutils.statemachine import ViewList
+from docutils.parsers.rst import Directive
+from sphinx.util.nodes import nested_parse_with_titles
+
+
+MODULEDOC = '''
+.. module:: %s
+
+%s
+%s
+'''
+
+LEXERDOC = '''
+.. class:: %s
+
+ :Short names: %s
+ :Filenames: %s
+ :MIME types: %s
+
+ %s
+
+'''
+
+FMTERDOC = '''
+.. class:: %s
+
+ :Short names: %s
+ :Filenames: %s
+
+ %s
+
+'''
+
+FILTERDOC = '''
+.. class:: %s
+
+ :Name: %s
+
+ %s
+
+'''
+
+
+class PygmentsDoc(Directive):
+ """
+ A directive to collect all lexers/formatters/filters and generate
+ autoclass directives for them.
+ """
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = False
+ option_spec = {}
+
+ def run(self):
+ self.filenames = set()
+ if self.arguments[0] == 'lexers':
+ out = self.document_lexers()
+ elif self.arguments[0] == 'formatters':
+ out = self.document_formatters()
+ elif self.arguments[0] == 'filters':
+ out = self.document_filters()
+ else:
+ raise Exception('invalid argument for "pygmentsdoc" directive')
+ node = nodes.compound()
+ vl = ViewList(out.split('\n'), source='')
+ nested_parse_with_titles(self.state, vl, node)
+ for fn in self.filenames:
+ self.state.document.settings.record_dependencies.add(fn)
+ return node.children
+
+ def document_lexers(self):
+ from pip._vendor.pygments.lexers._mapping import LEXERS
+ out = []
+ modules = {}
+ moduledocstrings = {}
+ for classname, data in sorted(LEXERS.items(), key=lambda x: x[0]):
+ module = data[0]
+ mod = __import__(module, None, None, [classname])
+ self.filenames.add(mod.__file__)
+ cls = getattr(mod, classname)
+ if not cls.__doc__:
+ print("Warning: %s does not have a docstring." % classname)
+ docstring = cls.__doc__
+ if isinstance(docstring, bytes):
+ docstring = docstring.decode('utf8')
+ modules.setdefault(module, []).append((
+ classname,
+ ', '.join(data[2]) or 'None',
+ ', '.join(data[3]).replace('*', '\\*').replace('_', '\\') or 'None',
+ ', '.join(data[4]) or 'None',
+ docstring))
+ if module not in moduledocstrings:
+ moddoc = mod.__doc__
+ if isinstance(moddoc, bytes):
+ moddoc = moddoc.decode('utf8')
+ moduledocstrings[module] = moddoc
+
+ for module, lexers in sorted(modules.items(), key=lambda x: x[0]):
+ if moduledocstrings[module] is None:
+ raise Exception("Missing docstring for %s" % (module,))
+ heading = moduledocstrings[module].splitlines()[4].strip().rstrip('.')
+ out.append(MODULEDOC % (module, heading, '-'*len(heading)))
+ for data in lexers:
+ out.append(LEXERDOC % data)
+
+ return ''.join(out)
+
+ def document_formatters(self):
+ from pip._vendor.pygments.formatters import FORMATTERS
+
+ out = []
+ for classname, data in sorted(FORMATTERS.items(), key=lambda x: x[0]):
+ module = data[0]
+ mod = __import__(module, None, None, [classname])
+ self.filenames.add(mod.__file__)
+ cls = getattr(mod, classname)
+ docstring = cls.__doc__
+ if isinstance(docstring, bytes):
+ docstring = docstring.decode('utf8')
+ heading = cls.__name__
+ out.append(FMTERDOC % (heading, ', '.join(data[2]) or 'None',
+ ', '.join(data[3]).replace('*', '\\*') or 'None',
+ docstring))
+ return ''.join(out)
+
+ def document_filters(self):
+ from pip._vendor.pygments.filters import FILTERS
+
+ out = []
+ for name, cls in FILTERS.items():
+ self.filenames.add(sys.modules[cls.__module__].__file__)
+ docstring = cls.__doc__
+ if isinstance(docstring, bytes):
+ docstring = docstring.decode('utf8')
+ out.append(FILTERDOC % (cls.__name__, name, docstring))
+ return ''.join(out)
+
+
+def setup(app):
+ app.add_directive('pygmentsdoc', PygmentsDoc)
diff --git a/src/pip/_vendor/pygments/style.py b/src/pip/_vendor/pygments/style.py
new file mode 100644
index 000000000..84abbc205
--- /dev/null
+++ b/src/pip/_vendor/pygments/style.py
@@ -0,0 +1,197 @@
+"""
+ pygments.style
+ ~~~~~~~~~~~~~~
+
+ Basic style object.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from pip._vendor.pygments.token import Token, STANDARD_TYPES
+
+# Default mapping of ansixxx to RGB colors.
+_ansimap = {
+ # dark
+ 'ansiblack': '000000',
+ 'ansired': '7f0000',
+ 'ansigreen': '007f00',
+ 'ansiyellow': '7f7fe0',
+ 'ansiblue': '00007f',
+ 'ansimagenta': '7f007f',
+ 'ansicyan': '007f7f',
+ 'ansigray': 'e5e5e5',
+ # normal
+ 'ansibrightblack': '555555',
+ 'ansibrightred': 'ff0000',
+ 'ansibrightgreen': '00ff00',
+ 'ansibrightyellow': 'ffff00',
+ 'ansibrightblue': '0000ff',
+ 'ansibrightmagenta': 'ff00ff',
+ 'ansibrightcyan': '00ffff',
+ 'ansiwhite': 'ffffff',
+}
+# mapping of deprecated #ansixxx colors to new color names
+_deprecated_ansicolors = {
+ # dark
+ '#ansiblack': 'ansiblack',
+ '#ansidarkred': 'ansired',
+ '#ansidarkgreen': 'ansigreen',
+ '#ansibrown': 'ansiyellow',
+ '#ansidarkblue': 'ansiblue',
+ '#ansipurple': 'ansimagenta',
+ '#ansiteal': 'ansicyan',
+ '#ansilightgray': 'ansigray',
+ # normal
+ '#ansidarkgray': 'ansibrightblack',
+ '#ansired': 'ansibrightred',
+ '#ansigreen': 'ansibrightgreen',
+ '#ansiyellow': 'ansibrightyellow',
+ '#ansiblue': 'ansibrightblue',
+ '#ansifuchsia': 'ansibrightmagenta',
+ '#ansiturquoise': 'ansibrightcyan',
+ '#ansiwhite': 'ansiwhite',
+}
+ansicolors = set(_ansimap)
+
+
+class StyleMeta(type):
+
+ def __new__(mcs, name, bases, dct):
+ obj = type.__new__(mcs, name, bases, dct)
+ for token in STANDARD_TYPES:
+ if token not in obj.styles:
+ obj.styles[token] = ''
+
+ def colorformat(text):
+ if text in ansicolors:
+ return text
+ if text[0:1] == '#':
+ col = text[1:]
+ if len(col) == 6:
+ return col
+ elif len(col) == 3:
+ return col[0] * 2 + col[1] * 2 + col[2] * 2
+ elif text == '':
+ return ''
+ elif text.startswith('var') or text.startswith('calc'):
+ return text
+ assert False, "wrong color format %r" % text
+
+ _styles = obj._styles = {}
+
+ for ttype in obj.styles:
+ for token in ttype.split():
+ if token in _styles:
+ continue
+ ndef = _styles.get(token.parent, None)
+ styledefs = obj.styles.get(token, '').split()
+ if not ndef or token is None:
+ ndef = ['', 0, 0, 0, '', '', 0, 0, 0]
+ elif 'noinherit' in styledefs and token is not Token:
+ ndef = _styles[Token][:]
+ else:
+ ndef = ndef[:]
+ _styles[token] = ndef
+ for styledef in obj.styles.get(token, '').split():
+ if styledef == 'noinherit':
+ pass
+ elif styledef == 'bold':
+ ndef[1] = 1
+ elif styledef == 'nobold':
+ ndef[1] = 0
+ elif styledef == 'italic':
+ ndef[2] = 1
+ elif styledef == 'noitalic':
+ ndef[2] = 0
+ elif styledef == 'underline':
+ ndef[3] = 1
+ elif styledef == 'nounderline':
+ ndef[3] = 0
+ elif styledef[:3] == 'bg:':
+ ndef[4] = colorformat(styledef[3:])
+ elif styledef[:7] == 'border:':
+ ndef[5] = colorformat(styledef[7:])
+ elif styledef == 'roman':
+ ndef[6] = 1
+ elif styledef == 'sans':
+ ndef[7] = 1
+ elif styledef == 'mono':
+ ndef[8] = 1
+ else:
+ ndef[0] = colorformat(styledef)
+
+ return obj
+
+ def style_for_token(cls, token):
+ t = cls._styles[token]
+ ansicolor = bgansicolor = None
+ color = t[0]
+ if color in _deprecated_ansicolors:
+ color = _deprecated_ansicolors[color]
+ if color in ansicolors:
+ ansicolor = color
+ color = _ansimap[color]
+ bgcolor = t[4]
+ if bgcolor in _deprecated_ansicolors:
+ bgcolor = _deprecated_ansicolors[bgcolor]
+ if bgcolor in ansicolors:
+ bgansicolor = bgcolor
+ bgcolor = _ansimap[bgcolor]
+
+ return {
+ 'color': color or None,
+ 'bold': bool(t[1]),
+ 'italic': bool(t[2]),
+ 'underline': bool(t[3]),
+ 'bgcolor': bgcolor or None,
+ 'border': t[5] or None,
+ 'roman': bool(t[6]) or None,
+ 'sans': bool(t[7]) or None,
+ 'mono': bool(t[8]) or None,
+ 'ansicolor': ansicolor,
+ 'bgansicolor': bgansicolor,
+ }
+
+ def list_styles(cls):
+ return list(cls)
+
+ def styles_token(cls, ttype):
+ return ttype in cls._styles
+
+ def __iter__(cls):
+ for token in cls._styles:
+ yield token, cls.style_for_token(token)
+
+ def __len__(cls):
+ return len(cls._styles)
+
+
+class Style(metaclass=StyleMeta):
+
+ #: overall background color (``None`` means transparent)
+ background_color = '#ffffff'
+
+ #: highlight background color
+ highlight_color = '#ffffcc'
+
+ #: line number font color
+ line_number_color = 'inherit'
+
+ #: line number background color
+ line_number_background_color = 'transparent'
+
+ #: special line number font color
+ line_number_special_color = '#000000'
+
+ #: special line number background color
+ line_number_special_background_color = '#ffffc0'
+
+ #: Style definitions for individual token types.
+ styles = {}
+
+ # Attribute for lexers defined within Pygments. If set
+ # to True, the style is not shown in the style gallery
+ # on the website. This is intended for language-specific
+ # styles.
+ web_style_gallery_exclude = False
diff --git a/src/pip/_vendor/pygments/styles/__init__.py b/src/pip/_vendor/pygments/styles/__init__.py
new file mode 100644
index 000000000..44cc0efb0
--- /dev/null
+++ b/src/pip/_vendor/pygments/styles/__init__.py
@@ -0,0 +1,97 @@
+"""
+ pygments.styles
+ ~~~~~~~~~~~~~~~
+
+ Contains built-in styles.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from pip._vendor.pygments.plugin import find_plugin_styles
+from pip._vendor.pygments.util import ClassNotFound
+
+
+#: Maps style names to 'submodule::classname'.
+STYLE_MAP = {
+ 'default': 'default::DefaultStyle',
+ 'emacs': 'emacs::EmacsStyle',
+ 'friendly': 'friendly::FriendlyStyle',
+ 'friendly_grayscale': 'friendly_grayscale::FriendlyGrayscaleStyle',
+ 'colorful': 'colorful::ColorfulStyle',
+ 'autumn': 'autumn::AutumnStyle',
+ 'murphy': 'murphy::MurphyStyle',
+ 'manni': 'manni::ManniStyle',
+ 'material': 'material::MaterialStyle',
+ 'monokai': 'monokai::MonokaiStyle',
+ 'perldoc': 'perldoc::PerldocStyle',
+ 'pastie': 'pastie::PastieStyle',
+ 'borland': 'borland::BorlandStyle',
+ 'trac': 'trac::TracStyle',
+ 'native': 'native::NativeStyle',
+ 'fruity': 'fruity::FruityStyle',
+ 'bw': 'bw::BlackWhiteStyle',
+ 'vim': 'vim::VimStyle',
+ 'vs': 'vs::VisualStudioStyle',
+ 'tango': 'tango::TangoStyle',
+ 'rrt': 'rrt::RrtStyle',
+ 'xcode': 'xcode::XcodeStyle',
+ 'igor': 'igor::IgorStyle',
+ 'paraiso-light': 'paraiso_light::ParaisoLightStyle',
+ 'paraiso-dark': 'paraiso_dark::ParaisoDarkStyle',
+ 'lovelace': 'lovelace::LovelaceStyle',
+ 'algol': 'algol::AlgolStyle',
+ 'algol_nu': 'algol_nu::Algol_NuStyle',
+ 'arduino': 'arduino::ArduinoStyle',
+ 'rainbow_dash': 'rainbow_dash::RainbowDashStyle',
+ 'abap': 'abap::AbapStyle',
+ 'solarized-dark': 'solarized::SolarizedDarkStyle',
+ 'solarized-light': 'solarized::SolarizedLightStyle',
+ 'sas': 'sas::SasStyle',
+ 'staroffice' : 'staroffice::StarofficeStyle',
+ 'stata': 'stata_light::StataLightStyle',
+ 'stata-light': 'stata_light::StataLightStyle',
+ 'stata-dark': 'stata_dark::StataDarkStyle',
+ 'inkpot': 'inkpot::InkPotStyle',
+ 'zenburn': 'zenburn::ZenburnStyle',
+ 'gruvbox-dark': 'gruvbox::GruvboxDarkStyle',
+ 'gruvbox-light': 'gruvbox::GruvboxLightStyle',
+ 'dracula': 'dracula::DraculaStyle',
+ 'one-dark': 'onedark::OneDarkStyle',
+ 'lilypond' : 'lilypond::LilyPondStyle',
+ 'nord': 'nord::NordStyle',
+ 'nord-darker': 'nord::NordDarkerStyle',
+ 'github-dark': 'gh_dark::GhDarkStyle'
+}
+
+
+def get_style_by_name(name):
+ if name in STYLE_MAP:
+ mod, cls = STYLE_MAP[name].split('::')
+ builtin = "yes"
+ else:
+ for found_name, style in find_plugin_styles():
+ if name == found_name:
+ return style
+ # perhaps it got dropped into our styles package
+ builtin = ""
+ mod = name
+ cls = name.title() + "Style"
+
+ try:
+ mod = __import__('pygments.styles.' + mod, None, None, [cls])
+ except ImportError:
+ raise ClassNotFound("Could not find style module %r" % mod +
+ (builtin and ", though it should be builtin") + ".")
+ try:
+ return getattr(mod, cls)
+ except AttributeError:
+ raise ClassNotFound("Could not find style class %r in style module." % cls)
+
+
+def get_all_styles():
+ """Return a generator for all styles by name,
+ both builtin and plugin."""
+ yield from STYLE_MAP
+ for name, _ in find_plugin_styles():
+ yield name
diff --git a/src/pip/_vendor/pygments/token.py b/src/pip/_vendor/pygments/token.py
new file mode 100644
index 000000000..e3e565ad5
--- /dev/null
+++ b/src/pip/_vendor/pygments/token.py
@@ -0,0 +1,213 @@
+"""
+ pygments.token
+ ~~~~~~~~~~~~~~
+
+ Basic token types and the standard tokens.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+
+class _TokenType(tuple):
+ parent = None
+
+ def split(self):
+ buf = []
+ node = self
+ while node is not None:
+ buf.append(node)
+ node = node.parent
+ buf.reverse()
+ return buf
+
+ def __init__(self, *args):
+ # no need to call super.__init__
+ self.subtypes = set()
+
+ def __contains__(self, val):
+ return self is val or (
+ type(val) is self.__class__ and
+ val[:len(self)] == self
+ )
+
+ def __getattr__(self, val):
+ if not val or not val[0].isupper():
+ return tuple.__getattribute__(self, val)
+ new = _TokenType(self + (val,))
+ setattr(self, val, new)
+ self.subtypes.add(new)
+ new.parent = self
+ return new
+
+ def __repr__(self):
+ return 'Token' + (self and '.' or '') + '.'.join(self)
+
+ def __copy__(self):
+ # These instances are supposed to be singletons
+ return self
+
+ def __deepcopy__(self, memo):
+ # These instances are supposed to be singletons
+ return self
+
+
+Token = _TokenType()
+
+# Special token types
+Text = Token.Text
+Whitespace = Text.Whitespace
+Escape = Token.Escape
+Error = Token.Error
+# Text that doesn't belong to this lexer (e.g. HTML in PHP)
+Other = Token.Other
+
+# Common token types for source code
+Keyword = Token.Keyword
+Name = Token.Name
+Literal = Token.Literal
+String = Literal.String
+Number = Literal.Number
+Punctuation = Token.Punctuation
+Operator = Token.Operator
+Comment = Token.Comment
+
+# Generic types for non-source code
+Generic = Token.Generic
+
+# String and some others are not direct children of Token.
+# alias them:
+Token.Token = Token
+Token.String = String
+Token.Number = Number
+
+
+def is_token_subtype(ttype, other):
+ """
+ Return True if ``ttype`` is a subtype of ``other``.
+
+ exists for backwards compatibility. use ``ttype in other`` now.
+ """
+ return ttype in other
+
+
+def string_to_tokentype(s):
+ """
+ Convert a string into a token type::
+
+ >>> string_to_token('String.Double')
+ Token.Literal.String.Double
+ >>> string_to_token('Token.Literal.Number')
+ Token.Literal.Number
+ >>> string_to_token('')
+ Token
+
+ Tokens that are already tokens are returned unchanged:
+
+ >>> string_to_token(String)
+ Token.Literal.String
+ """
+ if isinstance(s, _TokenType):
+ return s
+ if not s:
+ return Token
+ node = Token
+ for item in s.split('.'):
+ node = getattr(node, item)
+ return node
+
+
+# Map standard token types to short names, used in CSS class naming.
+# If you add a new item, please be sure to run this file to perform
+# a consistency check for duplicate values.
+STANDARD_TYPES = {
+ Token: '',
+
+ Text: '',
+ Whitespace: 'w',
+ Escape: 'esc',
+ Error: 'err',
+ Other: 'x',
+
+ Keyword: 'k',
+ Keyword.Constant: 'kc',
+ Keyword.Declaration: 'kd',
+ Keyword.Namespace: 'kn',
+ Keyword.Pseudo: 'kp',
+ Keyword.Reserved: 'kr',
+ Keyword.Type: 'kt',
+
+ Name: 'n',
+ Name.Attribute: 'na',
+ Name.Builtin: 'nb',
+ Name.Builtin.Pseudo: 'bp',
+ Name.Class: 'nc',
+ Name.Constant: 'no',
+ Name.Decorator: 'nd',
+ Name.Entity: 'ni',
+ Name.Exception: 'ne',
+ Name.Function: 'nf',
+ Name.Function.Magic: 'fm',
+ Name.Property: 'py',
+ Name.Label: 'nl',
+ Name.Namespace: 'nn',
+ Name.Other: 'nx',
+ Name.Tag: 'nt',
+ Name.Variable: 'nv',
+ Name.Variable.Class: 'vc',
+ Name.Variable.Global: 'vg',
+ Name.Variable.Instance: 'vi',
+ Name.Variable.Magic: 'vm',
+
+ Literal: 'l',
+ Literal.Date: 'ld',
+
+ String: 's',
+ String.Affix: 'sa',
+ String.Backtick: 'sb',
+ String.Char: 'sc',
+ String.Delimiter: 'dl',
+ String.Doc: 'sd',
+ String.Double: 's2',
+ String.Escape: 'se',
+ String.Heredoc: 'sh',
+ String.Interpol: 'si',
+ String.Other: 'sx',
+ String.Regex: 'sr',
+ String.Single: 's1',
+ String.Symbol: 'ss',
+
+ Number: 'm',
+ Number.Bin: 'mb',
+ Number.Float: 'mf',
+ Number.Hex: 'mh',
+ Number.Integer: 'mi',
+ Number.Integer.Long: 'il',
+ Number.Oct: 'mo',
+
+ Operator: 'o',
+ Operator.Word: 'ow',
+
+ Punctuation: 'p',
+ Punctuation.Marker: 'pm',
+
+ Comment: 'c',
+ Comment.Hashbang: 'ch',
+ Comment.Multiline: 'cm',
+ Comment.Preproc: 'cp',
+ Comment.PreprocFile: 'cpf',
+ Comment.Single: 'c1',
+ Comment.Special: 'cs',
+
+ Generic: 'g',
+ Generic.Deleted: 'gd',
+ Generic.Emph: 'ge',
+ Generic.Error: 'gr',
+ Generic.Heading: 'gh',
+ Generic.Inserted: 'gi',
+ Generic.Output: 'go',
+ Generic.Prompt: 'gp',
+ Generic.Strong: 'gs',
+ Generic.Subheading: 'gu',
+ Generic.Traceback: 'gt',
+}
diff --git a/src/pip/_vendor/pygments/unistring.py b/src/pip/_vendor/pygments/unistring.py
new file mode 100644
index 000000000..2e3c80869
--- /dev/null
+++ b/src/pip/_vendor/pygments/unistring.py
@@ -0,0 +1,153 @@
+"""
+ pygments.unistring
+ ~~~~~~~~~~~~~~~~~~
+
+ Strings of all Unicode characters of a certain category.
+ Used for matching in Unicode-aware languages. Run to regenerate.
+
+ Inspired by chartypes_create.py from the MoinMoin project.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+Cc = '\x00-\x1f\x7f-\x9f'
+
+Cf = '\xad\u0600-\u0605\u061c\u06dd\u070f\u08e2\u180e\u200b-\u200f\u202a-\u202e\u2060-\u2064\u2066-\u206f\ufeff\ufff9-\ufffb\U000110bd\U000110cd\U0001bca0-\U0001bca3\U0001d173-\U0001d17a\U000e0001\U000e0020-\U000e007f'
+
+Cn = '\u0378-\u0379\u0380-\u0383\u038b\u038d\u03a2\u0530\u0557-\u0558\u058b-\u058c\u0590\u05c8-\u05cf\u05eb-\u05ee\u05f5-\u05ff\u061d\u070e\u074b-\u074c\u07b2-\u07bf\u07fb-\u07fc\u082e-\u082f\u083f\u085c-\u085d\u085f\u086b-\u089f\u08b5\u08be-\u08d2\u0984\u098d-\u098e\u0991-\u0992\u09a9\u09b1\u09b3-\u09b5\u09ba-\u09bb\u09c5-\u09c6\u09c9-\u09ca\u09cf-\u09d6\u09d8-\u09db\u09de\u09e4-\u09e5\u09ff-\u0a00\u0a04\u0a0b-\u0a0e\u0a11-\u0a12\u0a29\u0a31\u0a34\u0a37\u0a3a-\u0a3b\u0a3d\u0a43-\u0a46\u0a49-\u0a4a\u0a4e-\u0a50\u0a52-\u0a58\u0a5d\u0a5f-\u0a65\u0a77-\u0a80\u0a84\u0a8e\u0a92\u0aa9\u0ab1\u0ab4\u0aba-\u0abb\u0ac6\u0aca\u0ace-\u0acf\u0ad1-\u0adf\u0ae4-\u0ae5\u0af2-\u0af8\u0b00\u0b04\u0b0d-\u0b0e\u0b11-\u0b12\u0b29\u0b31\u0b34\u0b3a-\u0b3b\u0b45-\u0b46\u0b49-\u0b4a\u0b4e-\u0b55\u0b58-\u0b5b\u0b5e\u0b64-\u0b65\u0b78-\u0b81\u0b84\u0b8b-\u0b8d\u0b91\u0b96-\u0b98\u0b9b\u0b9d\u0ba0-\u0ba2\u0ba5-\u0ba7\u0bab-\u0bad\u0bba-\u0bbd\u0bc3-\u0bc5\u0bc9\u0bce-\u0bcf\u0bd1-\u0bd6\u0bd8-\u0be5\u0bfb-\u0bff\u0c0d\u0c11\u0c29\u0c3a-\u0c3c\u0c45\u0c49\u0c4e-\u0c54\u0c57\u0c5b-\u0c5f\u0c64-\u0c65\u0c70-\u0c77\u0c8d\u0c91\u0ca9\u0cb4\u0cba-\u0cbb\u0cc5\u0cc9\u0cce-\u0cd4\u0cd7-\u0cdd\u0cdf\u0ce4-\u0ce5\u0cf0\u0cf3-\u0cff\u0d04\u0d0d\u0d11\u0d45\u0d49\u0d50-\u0d53\u0d64-\u0d65\u0d80-\u0d81\u0d84\u0d97-\u0d99\u0db2\u0dbc\u0dbe-\u0dbf\u0dc7-\u0dc9\u0dcb-\u0dce\u0dd5\u0dd7\u0de0-\u0de5\u0df0-\u0df1\u0df5-\u0e00\u0e3b-\u0e3e\u0e5c-\u0e80\u0e83\u0e85-\u0e86\u0e89\u0e8b-\u0e8c\u0e8e-\u0e93\u0e98\u0ea0\u0ea4\u0ea6\u0ea8-\u0ea9\u0eac\u0eba\u0ebe-\u0ebf\u0ec5\u0ec7\u0ece-\u0ecf\u0eda-\u0edb\u0ee0-\u0eff\u0f48\u0f6d-\u0f70\u0f98\u0fbd\u0fcd\u0fdb-\u0fff\u10c6\u10c8-\u10cc\u10ce-\u10cf\u1249\u124e-\u124f\u1257\u1259\u125e-\u125f\u1289\u128e-\u128f\u12b1\u12b6-\u12b7\u12bf\u12c1\u12c6-\u12c7\u12d7\u1311\u1316-\u1317\u135b-\u135c\u137d-\u137f\u139a-\u139f\u13f6-\u13f7\u13fe-\u13ff\u169d-\u169f\u16f9-\u16ff\u170d\u1715-\u171f\u1737-\u173f\u1754-\u175f\u176d\u1771\u1774-\u177f\u17de-\u17df\u17ea-\u17ef\u17fa-\u17ff\u180f\u181a-\u181f\u1879-\u187f\u18ab-\u18af\u18f6-\u18ff\u191f\u192c-\u192f\u193c-\u193f\u1941-\u1943\u196e-\u196f\u1975-\u197f\u19ac-\u19af\u19ca-\u19cf\u19db-\u19dd\u1a1c-\u1a1d\u1a5f\u1a7d-\u1a7e\u1a8a-\u1a8f\u1a9a-\u1a9f\u1aae-\u1aaf\u1abf-\u1aff\u1b4c-\u1b4f\u1b7d-\u1b7f\u1bf4-\u1bfb\u1c38-\u1c3a\u1c4a-\u1c4c\u1c89-\u1c8f\u1cbb-\u1cbc\u1cc8-\u1ccf\u1cfa-\u1cff\u1dfa\u1f16-\u1f17\u1f1e-\u1f1f\u1f46-\u1f47\u1f4e-\u1f4f\u1f58\u1f5a\u1f5c\u1f5e\u1f7e-\u1f7f\u1fb5\u1fc5\u1fd4-\u1fd5\u1fdc\u1ff0-\u1ff1\u1ff5\u1fff\u2065\u2072-\u2073\u208f\u209d-\u209f\u20c0-\u20cf\u20f1-\u20ff\u218c-\u218f\u2427-\u243f\u244b-\u245f\u2b74-\u2b75\u2b96-\u2b97\u2bc9\u2bff\u2c2f\u2c5f\u2cf4-\u2cf8\u2d26\u2d28-\u2d2c\u2d2e-\u2d2f\u2d68-\u2d6e\u2d71-\u2d7e\u2d97-\u2d9f\u2da7\u2daf\u2db7\u2dbf\u2dc7\u2dcf\u2dd7\u2ddf\u2e4f-\u2e7f\u2e9a\u2ef4-\u2eff\u2fd6-\u2fef\u2ffc-\u2fff\u3040\u3097-\u3098\u3100-\u3104\u3130\u318f\u31bb-\u31bf\u31e4-\u31ef\u321f\u32ff\u4db6-\u4dbf\u9ff0-\u9fff\ua48d-\ua48f\ua4c7-\ua4cf\ua62c-\ua63f\ua6f8-\ua6ff\ua7ba-\ua7f6\ua82c-\ua82f\ua83a-\ua83f\ua878-\ua87f\ua8c6-\ua8cd\ua8da-\ua8df\ua954-\ua95e\ua97d-\ua97f\ua9ce\ua9da-\ua9dd\ua9ff\uaa37-\uaa3f\uaa4e-\uaa4f\uaa5a-\uaa5b\uaac3-\uaada\uaaf7-\uab00\uab07-\uab08\uab0f-\uab10\uab17-\uab1f\uab27\uab2f\uab66-\uab6f\uabee-\uabef\uabfa-\uabff\ud7a4-\ud7af\ud7c7-\ud7ca\ud7fc-\ud7ff\ufa6e-\ufa6f\ufada-\ufaff\ufb07-\ufb12\ufb18-\ufb1c\ufb37\ufb3d\ufb3f\ufb42\ufb45\ufbc2-\ufbd2\ufd40-\ufd4f\ufd90-\ufd91\ufdc8-\ufdef\ufdfe-\ufdff\ufe1a-\ufe1f\ufe53\ufe67\ufe6c-\ufe6f\ufe75\ufefd-\ufefe\uff00\uffbf-\uffc1\uffc8-\uffc9\uffd0-\uffd1\uffd8-\uffd9\uffdd-\uffdf\uffe7\uffef-\ufff8\ufffe-\uffff\U0001000c\U00010027\U0001003b\U0001003e\U0001004e-\U0001004f\U0001005e-\U0001007f\U000100fb-\U000100ff\U00010103-\U00010106\U00010134-\U00010136\U0001018f\U0001019c-\U0001019f\U000101a1-\U000101cf\U000101fe-\U0001027f\U0001029d-\U0001029f\U000102d1-\U000102df\U000102fc-\U000102ff\U00010324-\U0001032c\U0001034b-\U0001034f\U0001037b-\U0001037f\U0001039e\U000103c4-\U000103c7\U000103d6-\U000103ff\U0001049e-\U0001049f\U000104aa-\U000104af\U000104d4-\U000104d7\U000104fc-\U000104ff\U00010528-\U0001052f\U00010564-\U0001056e\U00010570-\U000105ff\U00010737-\U0001073f\U00010756-\U0001075f\U00010768-\U000107ff\U00010806-\U00010807\U00010809\U00010836\U00010839-\U0001083b\U0001083d-\U0001083e\U00010856\U0001089f-\U000108a6\U000108b0-\U000108df\U000108f3\U000108f6-\U000108fa\U0001091c-\U0001091e\U0001093a-\U0001093e\U00010940-\U0001097f\U000109b8-\U000109bb\U000109d0-\U000109d1\U00010a04\U00010a07-\U00010a0b\U00010a14\U00010a18\U00010a36-\U00010a37\U00010a3b-\U00010a3e\U00010a49-\U00010a4f\U00010a59-\U00010a5f\U00010aa0-\U00010abf\U00010ae7-\U00010aea\U00010af7-\U00010aff\U00010b36-\U00010b38\U00010b56-\U00010b57\U00010b73-\U00010b77\U00010b92-\U00010b98\U00010b9d-\U00010ba8\U00010bb0-\U00010bff\U00010c49-\U00010c7f\U00010cb3-\U00010cbf\U00010cf3-\U00010cf9\U00010d28-\U00010d2f\U00010d3a-\U00010e5f\U00010e7f-\U00010eff\U00010f28-\U00010f2f\U00010f5a-\U00010fff\U0001104e-\U00011051\U00011070-\U0001107e\U000110c2-\U000110cc\U000110ce-\U000110cf\U000110e9-\U000110ef\U000110fa-\U000110ff\U00011135\U00011147-\U0001114f\U00011177-\U0001117f\U000111ce-\U000111cf\U000111e0\U000111f5-\U000111ff\U00011212\U0001123f-\U0001127f\U00011287\U00011289\U0001128e\U0001129e\U000112aa-\U000112af\U000112eb-\U000112ef\U000112fa-\U000112ff\U00011304\U0001130d-\U0001130e\U00011311-\U00011312\U00011329\U00011331\U00011334\U0001133a\U00011345-\U00011346\U00011349-\U0001134a\U0001134e-\U0001134f\U00011351-\U00011356\U00011358-\U0001135c\U00011364-\U00011365\U0001136d-\U0001136f\U00011375-\U000113ff\U0001145a\U0001145c\U0001145f-\U0001147f\U000114c8-\U000114cf\U000114da-\U0001157f\U000115b6-\U000115b7\U000115de-\U000115ff\U00011645-\U0001164f\U0001165a-\U0001165f\U0001166d-\U0001167f\U000116b8-\U000116bf\U000116ca-\U000116ff\U0001171b-\U0001171c\U0001172c-\U0001172f\U00011740-\U000117ff\U0001183c-\U0001189f\U000118f3-\U000118fe\U00011900-\U000119ff\U00011a48-\U00011a4f\U00011a84-\U00011a85\U00011aa3-\U00011abf\U00011af9-\U00011bff\U00011c09\U00011c37\U00011c46-\U00011c4f\U00011c6d-\U00011c6f\U00011c90-\U00011c91\U00011ca8\U00011cb7-\U00011cff\U00011d07\U00011d0a\U00011d37-\U00011d39\U00011d3b\U00011d3e\U00011d48-\U00011d4f\U00011d5a-\U00011d5f\U00011d66\U00011d69\U00011d8f\U00011d92\U00011d99-\U00011d9f\U00011daa-\U00011edf\U00011ef9-\U00011fff\U0001239a-\U000123ff\U0001246f\U00012475-\U0001247f\U00012544-\U00012fff\U0001342f-\U000143ff\U00014647-\U000167ff\U00016a39-\U00016a3f\U00016a5f\U00016a6a-\U00016a6d\U00016a70-\U00016acf\U00016aee-\U00016aef\U00016af6-\U00016aff\U00016b46-\U00016b4f\U00016b5a\U00016b62\U00016b78-\U00016b7c\U00016b90-\U00016e3f\U00016e9b-\U00016eff\U00016f45-\U00016f4f\U00016f7f-\U00016f8e\U00016fa0-\U00016fdf\U00016fe2-\U00016fff\U000187f2-\U000187ff\U00018af3-\U0001afff\U0001b11f-\U0001b16f\U0001b2fc-\U0001bbff\U0001bc6b-\U0001bc6f\U0001bc7d-\U0001bc7f\U0001bc89-\U0001bc8f\U0001bc9a-\U0001bc9b\U0001bca4-\U0001cfff\U0001d0f6-\U0001d0ff\U0001d127-\U0001d128\U0001d1e9-\U0001d1ff\U0001d246-\U0001d2df\U0001d2f4-\U0001d2ff\U0001d357-\U0001d35f\U0001d379-\U0001d3ff\U0001d455\U0001d49d\U0001d4a0-\U0001d4a1\U0001d4a3-\U0001d4a4\U0001d4a7-\U0001d4a8\U0001d4ad\U0001d4ba\U0001d4bc\U0001d4c4\U0001d506\U0001d50b-\U0001d50c\U0001d515\U0001d51d\U0001d53a\U0001d53f\U0001d545\U0001d547-\U0001d549\U0001d551\U0001d6a6-\U0001d6a7\U0001d7cc-\U0001d7cd\U0001da8c-\U0001da9a\U0001daa0\U0001dab0-\U0001dfff\U0001e007\U0001e019-\U0001e01a\U0001e022\U0001e025\U0001e02b-\U0001e7ff\U0001e8c5-\U0001e8c6\U0001e8d7-\U0001e8ff\U0001e94b-\U0001e94f\U0001e95a-\U0001e95d\U0001e960-\U0001ec70\U0001ecb5-\U0001edff\U0001ee04\U0001ee20\U0001ee23\U0001ee25-\U0001ee26\U0001ee28\U0001ee33\U0001ee38\U0001ee3a\U0001ee3c-\U0001ee41\U0001ee43-\U0001ee46\U0001ee48\U0001ee4a\U0001ee4c\U0001ee50\U0001ee53\U0001ee55-\U0001ee56\U0001ee58\U0001ee5a\U0001ee5c\U0001ee5e\U0001ee60\U0001ee63\U0001ee65-\U0001ee66\U0001ee6b\U0001ee73\U0001ee78\U0001ee7d\U0001ee7f\U0001ee8a\U0001ee9c-\U0001eea0\U0001eea4\U0001eeaa\U0001eebc-\U0001eeef\U0001eef2-\U0001efff\U0001f02c-\U0001f02f\U0001f094-\U0001f09f\U0001f0af-\U0001f0b0\U0001f0c0\U0001f0d0\U0001f0f6-\U0001f0ff\U0001f10d-\U0001f10f\U0001f16c-\U0001f16f\U0001f1ad-\U0001f1e5\U0001f203-\U0001f20f\U0001f23c-\U0001f23f\U0001f249-\U0001f24f\U0001f252-\U0001f25f\U0001f266-\U0001f2ff\U0001f6d5-\U0001f6df\U0001f6ed-\U0001f6ef\U0001f6fa-\U0001f6ff\U0001f774-\U0001f77f\U0001f7d9-\U0001f7ff\U0001f80c-\U0001f80f\U0001f848-\U0001f84f\U0001f85a-\U0001f85f\U0001f888-\U0001f88f\U0001f8ae-\U0001f8ff\U0001f90c-\U0001f90f\U0001f93f\U0001f971-\U0001f972\U0001f977-\U0001f979\U0001f97b\U0001f9a3-\U0001f9af\U0001f9ba-\U0001f9bf\U0001f9c3-\U0001f9cf\U0001fa00-\U0001fa5f\U0001fa6e-\U0001ffff\U0002a6d7-\U0002a6ff\U0002b735-\U0002b73f\U0002b81e-\U0002b81f\U0002cea2-\U0002ceaf\U0002ebe1-\U0002f7ff\U0002fa1e-\U000e0000\U000e0002-\U000e001f\U000e0080-\U000e00ff\U000e01f0-\U000effff\U000ffffe-\U000fffff\U0010fffe-\U0010ffff'
+
+Co = '\ue000-\uf8ff\U000f0000-\U000ffffd\U00100000-\U0010fffd'
+
+Cs = '\ud800-\udbff\\\udc00\udc01-\udfff'
+
+Ll = 'a-z\xb5\xdf-\xf6\xf8-\xff\u0101\u0103\u0105\u0107\u0109\u010b\u010d\u010f\u0111\u0113\u0115\u0117\u0119\u011b\u011d\u011f\u0121\u0123\u0125\u0127\u0129\u012b\u012d\u012f\u0131\u0133\u0135\u0137-\u0138\u013a\u013c\u013e\u0140\u0142\u0144\u0146\u0148-\u0149\u014b\u014d\u014f\u0151\u0153\u0155\u0157\u0159\u015b\u015d\u015f\u0161\u0163\u0165\u0167\u0169\u016b\u016d\u016f\u0171\u0173\u0175\u0177\u017a\u017c\u017e-\u0180\u0183\u0185\u0188\u018c-\u018d\u0192\u0195\u0199-\u019b\u019e\u01a1\u01a3\u01a5\u01a8\u01aa-\u01ab\u01ad\u01b0\u01b4\u01b6\u01b9-\u01ba\u01bd-\u01bf\u01c6\u01c9\u01cc\u01ce\u01d0\u01d2\u01d4\u01d6\u01d8\u01da\u01dc-\u01dd\u01df\u01e1\u01e3\u01e5\u01e7\u01e9\u01eb\u01ed\u01ef-\u01f0\u01f3\u01f5\u01f9\u01fb\u01fd\u01ff\u0201\u0203\u0205\u0207\u0209\u020b\u020d\u020f\u0211\u0213\u0215\u0217\u0219\u021b\u021d\u021f\u0221\u0223\u0225\u0227\u0229\u022b\u022d\u022f\u0231\u0233-\u0239\u023c\u023f-\u0240\u0242\u0247\u0249\u024b\u024d\u024f-\u0293\u0295-\u02af\u0371\u0373\u0377\u037b-\u037d\u0390\u03ac-\u03ce\u03d0-\u03d1\u03d5-\u03d7\u03d9\u03db\u03dd\u03df\u03e1\u03e3\u03e5\u03e7\u03e9\u03eb\u03ed\u03ef-\u03f3\u03f5\u03f8\u03fb-\u03fc\u0430-\u045f\u0461\u0463\u0465\u0467\u0469\u046b\u046d\u046f\u0471\u0473\u0475\u0477\u0479\u047b\u047d\u047f\u0481\u048b\u048d\u048f\u0491\u0493\u0495\u0497\u0499\u049b\u049d\u049f\u04a1\u04a3\u04a5\u04a7\u04a9\u04ab\u04ad\u04af\u04b1\u04b3\u04b5\u04b7\u04b9\u04bb\u04bd\u04bf\u04c2\u04c4\u04c6\u04c8\u04ca\u04cc\u04ce-\u04cf\u04d1\u04d3\u04d5\u04d7\u04d9\u04db\u04dd\u04df\u04e1\u04e3\u04e5\u04e7\u04e9\u04eb\u04ed\u04ef\u04f1\u04f3\u04f5\u04f7\u04f9\u04fb\u04fd\u04ff\u0501\u0503\u0505\u0507\u0509\u050b\u050d\u050f\u0511\u0513\u0515\u0517\u0519\u051b\u051d\u051f\u0521\u0523\u0525\u0527\u0529\u052b\u052d\u052f\u0560-\u0588\u10d0-\u10fa\u10fd-\u10ff\u13f8-\u13fd\u1c80-\u1c88\u1d00-\u1d2b\u1d6b-\u1d77\u1d79-\u1d9a\u1e01\u1e03\u1e05\u1e07\u1e09\u1e0b\u1e0d\u1e0f\u1e11\u1e13\u1e15\u1e17\u1e19\u1e1b\u1e1d\u1e1f\u1e21\u1e23\u1e25\u1e27\u1e29\u1e2b\u1e2d\u1e2f\u1e31\u1e33\u1e35\u1e37\u1e39\u1e3b\u1e3d\u1e3f\u1e41\u1e43\u1e45\u1e47\u1e49\u1e4b\u1e4d\u1e4f\u1e51\u1e53\u1e55\u1e57\u1e59\u1e5b\u1e5d\u1e5f\u1e61\u1e63\u1e65\u1e67\u1e69\u1e6b\u1e6d\u1e6f\u1e71\u1e73\u1e75\u1e77\u1e79\u1e7b\u1e7d\u1e7f\u1e81\u1e83\u1e85\u1e87\u1e89\u1e8b\u1e8d\u1e8f\u1e91\u1e93\u1e95-\u1e9d\u1e9f\u1ea1\u1ea3\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u1eb9\u1ebb\u1ebd\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u1ec9\u1ecb\u1ecd\u1ecf\u1ed1\u1ed3\u1ed5\u1ed7\u1ed9\u1edb\u1edd\u1edf\u1ee1\u1ee3\u1ee5\u1ee7\u1ee9\u1eeb\u1eed\u1eef\u1ef1\u1ef3\u1ef5\u1ef7\u1ef9\u1efb\u1efd\u1eff-\u1f07\u1f10-\u1f15\u1f20-\u1f27\u1f30-\u1f37\u1f40-\u1f45\u1f50-\u1f57\u1f60-\u1f67\u1f70-\u1f7d\u1f80-\u1f87\u1f90-\u1f97\u1fa0-\u1fa7\u1fb0-\u1fb4\u1fb6-\u1fb7\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fc7\u1fd0-\u1fd3\u1fd6-\u1fd7\u1fe0-\u1fe7\u1ff2-\u1ff4\u1ff6-\u1ff7\u210a\u210e-\u210f\u2113\u212f\u2134\u2139\u213c-\u213d\u2146-\u2149\u214e\u2184\u2c30-\u2c5e\u2c61\u2c65-\u2c66\u2c68\u2c6a\u2c6c\u2c71\u2c73-\u2c74\u2c76-\u2c7b\u2c81\u2c83\u2c85\u2c87\u2c89\u2c8b\u2c8d\u2c8f\u2c91\u2c93\u2c95\u2c97\u2c99\u2c9b\u2c9d\u2c9f\u2ca1\u2ca3\u2ca5\u2ca7\u2ca9\u2cab\u2cad\u2caf\u2cb1\u2cb3\u2cb5\u2cb7\u2cb9\u2cbb\u2cbd\u2cbf\u2cc1\u2cc3\u2cc5\u2cc7\u2cc9\u2ccb\u2ccd\u2ccf\u2cd1\u2cd3\u2cd5\u2cd7\u2cd9\u2cdb\u2cdd\u2cdf\u2ce1\u2ce3-\u2ce4\u2cec\u2cee\u2cf3\u2d00-\u2d25\u2d27\u2d2d\ua641\ua643\ua645\ua647\ua649\ua64b\ua64d\ua64f\ua651\ua653\ua655\ua657\ua659\ua65b\ua65d\ua65f\ua661\ua663\ua665\ua667\ua669\ua66b\ua66d\ua681\ua683\ua685\ua687\ua689\ua68b\ua68d\ua68f\ua691\ua693\ua695\ua697\ua699\ua69b\ua723\ua725\ua727\ua729\ua72b\ua72d\ua72f-\ua731\ua733\ua735\ua737\ua739\ua73b\ua73d\ua73f\ua741\ua743\ua745\ua747\ua749\ua74b\ua74d\ua74f\ua751\ua753\ua755\ua757\ua759\ua75b\ua75d\ua75f\ua761\ua763\ua765\ua767\ua769\ua76b\ua76d\ua76f\ua771-\ua778\ua77a\ua77c\ua77f\ua781\ua783\ua785\ua787\ua78c\ua78e\ua791\ua793-\ua795\ua797\ua799\ua79b\ua79d\ua79f\ua7a1\ua7a3\ua7a5\ua7a7\ua7a9\ua7af\ua7b5\ua7b7\ua7b9\ua7fa\uab30-\uab5a\uab60-\uab65\uab70-\uabbf\ufb00-\ufb06\ufb13-\ufb17\uff41-\uff5a\U00010428-\U0001044f\U000104d8-\U000104fb\U00010cc0-\U00010cf2\U000118c0-\U000118df\U00016e60-\U00016e7f\U0001d41a-\U0001d433\U0001d44e-\U0001d454\U0001d456-\U0001d467\U0001d482-\U0001d49b\U0001d4b6-\U0001d4b9\U0001d4bb\U0001d4bd-\U0001d4c3\U0001d4c5-\U0001d4cf\U0001d4ea-\U0001d503\U0001d51e-\U0001d537\U0001d552-\U0001d56b\U0001d586-\U0001d59f\U0001d5ba-\U0001d5d3\U0001d5ee-\U0001d607\U0001d622-\U0001d63b\U0001d656-\U0001d66f\U0001d68a-\U0001d6a5\U0001d6c2-\U0001d6da\U0001d6dc-\U0001d6e1\U0001d6fc-\U0001d714\U0001d716-\U0001d71b\U0001d736-\U0001d74e\U0001d750-\U0001d755\U0001d770-\U0001d788\U0001d78a-\U0001d78f\U0001d7aa-\U0001d7c2\U0001d7c4-\U0001d7c9\U0001d7cb\U0001e922-\U0001e943'
+
+Lm = '\u02b0-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0374\u037a\u0559\u0640\u06e5-\u06e6\u07f4-\u07f5\u07fa\u081a\u0824\u0828\u0971\u0e46\u0ec6\u10fc\u17d7\u1843\u1aa7\u1c78-\u1c7d\u1d2c-\u1d6a\u1d78\u1d9b-\u1dbf\u2071\u207f\u2090-\u209c\u2c7c-\u2c7d\u2d6f\u2e2f\u3005\u3031-\u3035\u303b\u309d-\u309e\u30fc-\u30fe\ua015\ua4f8-\ua4fd\ua60c\ua67f\ua69c-\ua69d\ua717-\ua71f\ua770\ua788\ua7f8-\ua7f9\ua9cf\ua9e6\uaa70\uaadd\uaaf3-\uaaf4\uab5c-\uab5f\uff70\uff9e-\uff9f\U00016b40-\U00016b43\U00016f93-\U00016f9f\U00016fe0-\U00016fe1'
+
+Lo = '\xaa\xba\u01bb\u01c0-\u01c3\u0294\u05d0-\u05ea\u05ef-\u05f2\u0620-\u063f\u0641-\u064a\u066e-\u066f\u0671-\u06d3\u06d5\u06ee-\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u0800-\u0815\u0840-\u0858\u0860-\u086a\u08a0-\u08b4\u08b6-\u08bd\u0904-\u0939\u093d\u0950\u0958-\u0961\u0972-\u0980\u0985-\u098c\u098f-\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc-\u09dd\u09df-\u09e1\u09f0-\u09f1\u09fc\u0a05-\u0a0a\u0a0f-\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32-\u0a33\u0a35-\u0a36\u0a38-\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2-\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0-\u0ae1\u0af9\u0b05-\u0b0c\u0b0f-\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32-\u0b33\u0b35-\u0b39\u0b3d\u0b5c-\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99-\u0b9a\u0b9c\u0b9e-\u0b9f\u0ba3-\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58-\u0c5a\u0c60-\u0c61\u0c80\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0-\u0ce1\u0cf1-\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d54-\u0d56\u0d5f-\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32-\u0e33\u0e40-\u0e45\u0e81-\u0e82\u0e84\u0e87-\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa-\u0eab\u0ead-\u0eb0\u0eb2-\u0eb3\u0ebd\u0ec0-\u0ec4\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065-\u1066\u106e-\u1070\u1075-\u1081\u108e\u1100-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16f1-\u16f8\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17dc\u1820-\u1842\u1844-\u1878\u1880-\u1884\u1887-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u1a00-\u1a16\u1a20-\u1a54\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae-\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c77\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5-\u1cf6\u2135-\u2138\u2d30-\u2d67\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3006\u303c\u3041-\u3096\u309f\u30a1-\u30fa\u30ff\u3105-\u312f\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fef\ua000-\ua014\ua016-\ua48c\ua4d0-\ua4f7\ua500-\ua60b\ua610-\ua61f\ua62a-\ua62b\ua66e\ua6a0-\ua6e5\ua78f\ua7f7\ua7fb-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua8fd-\ua8fe\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9e0-\ua9e4\ua9e7-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa6f\uaa71-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5-\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadc\uaae0-\uaaea\uaaf2\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff66-\uff6f\uff71-\uff9d\uffa0-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc\U00010000-\U0001000b\U0001000d-\U00010026\U00010028-\U0001003a\U0001003c-\U0001003d\U0001003f-\U0001004d\U00010050-\U0001005d\U00010080-\U000100fa\U00010280-\U0001029c\U000102a0-\U000102d0\U00010300-\U0001031f\U0001032d-\U00010340\U00010342-\U00010349\U00010350-\U00010375\U00010380-\U0001039d\U000103a0-\U000103c3\U000103c8-\U000103cf\U00010450-\U0001049d\U00010500-\U00010527\U00010530-\U00010563\U00010600-\U00010736\U00010740-\U00010755\U00010760-\U00010767\U00010800-\U00010805\U00010808\U0001080a-\U00010835\U00010837-\U00010838\U0001083c\U0001083f-\U00010855\U00010860-\U00010876\U00010880-\U0001089e\U000108e0-\U000108f2\U000108f4-\U000108f5\U00010900-\U00010915\U00010920-\U00010939\U00010980-\U000109b7\U000109be-\U000109bf\U00010a00\U00010a10-\U00010a13\U00010a15-\U00010a17\U00010a19-\U00010a35\U00010a60-\U00010a7c\U00010a80-\U00010a9c\U00010ac0-\U00010ac7\U00010ac9-\U00010ae4\U00010b00-\U00010b35\U00010b40-\U00010b55\U00010b60-\U00010b72\U00010b80-\U00010b91\U00010c00-\U00010c48\U00010d00-\U00010d23\U00010f00-\U00010f1c\U00010f27\U00010f30-\U00010f45\U00011003-\U00011037\U00011083-\U000110af\U000110d0-\U000110e8\U00011103-\U00011126\U00011144\U00011150-\U00011172\U00011176\U00011183-\U000111b2\U000111c1-\U000111c4\U000111da\U000111dc\U00011200-\U00011211\U00011213-\U0001122b\U00011280-\U00011286\U00011288\U0001128a-\U0001128d\U0001128f-\U0001129d\U0001129f-\U000112a8\U000112b0-\U000112de\U00011305-\U0001130c\U0001130f-\U00011310\U00011313-\U00011328\U0001132a-\U00011330\U00011332-\U00011333\U00011335-\U00011339\U0001133d\U00011350\U0001135d-\U00011361\U00011400-\U00011434\U00011447-\U0001144a\U00011480-\U000114af\U000114c4-\U000114c5\U000114c7\U00011580-\U000115ae\U000115d8-\U000115db\U00011600-\U0001162f\U00011644\U00011680-\U000116aa\U00011700-\U0001171a\U00011800-\U0001182b\U000118ff\U00011a00\U00011a0b-\U00011a32\U00011a3a\U00011a50\U00011a5c-\U00011a83\U00011a86-\U00011a89\U00011a9d\U00011ac0-\U00011af8\U00011c00-\U00011c08\U00011c0a-\U00011c2e\U00011c40\U00011c72-\U00011c8f\U00011d00-\U00011d06\U00011d08-\U00011d09\U00011d0b-\U00011d30\U00011d46\U00011d60-\U00011d65\U00011d67-\U00011d68\U00011d6a-\U00011d89\U00011d98\U00011ee0-\U00011ef2\U00012000-\U00012399\U00012480-\U00012543\U00013000-\U0001342e\U00014400-\U00014646\U00016800-\U00016a38\U00016a40-\U00016a5e\U00016ad0-\U00016aed\U00016b00-\U00016b2f\U00016b63-\U00016b77\U00016b7d-\U00016b8f\U00016f00-\U00016f44\U00016f50\U00017000-\U000187f1\U00018800-\U00018af2\U0001b000-\U0001b11e\U0001b170-\U0001b2fb\U0001bc00-\U0001bc6a\U0001bc70-\U0001bc7c\U0001bc80-\U0001bc88\U0001bc90-\U0001bc99\U0001e800-\U0001e8c4\U0001ee00-\U0001ee03\U0001ee05-\U0001ee1f\U0001ee21-\U0001ee22\U0001ee24\U0001ee27\U0001ee29-\U0001ee32\U0001ee34-\U0001ee37\U0001ee39\U0001ee3b\U0001ee42\U0001ee47\U0001ee49\U0001ee4b\U0001ee4d-\U0001ee4f\U0001ee51-\U0001ee52\U0001ee54\U0001ee57\U0001ee59\U0001ee5b\U0001ee5d\U0001ee5f\U0001ee61-\U0001ee62\U0001ee64\U0001ee67-\U0001ee6a\U0001ee6c-\U0001ee72\U0001ee74-\U0001ee77\U0001ee79-\U0001ee7c\U0001ee7e\U0001ee80-\U0001ee89\U0001ee8b-\U0001ee9b\U0001eea1-\U0001eea3\U0001eea5-\U0001eea9\U0001eeab-\U0001eebb\U00020000-\U0002a6d6\U0002a700-\U0002b734\U0002b740-\U0002b81d\U0002b820-\U0002cea1\U0002ceb0-\U0002ebe0\U0002f800-\U0002fa1d'
+
+Lt = '\u01c5\u01c8\u01cb\u01f2\u1f88-\u1f8f\u1f98-\u1f9f\u1fa8-\u1faf\u1fbc\u1fcc\u1ffc'
+
+Lu = 'A-Z\xc0-\xd6\xd8-\xde\u0100\u0102\u0104\u0106\u0108\u010a\u010c\u010e\u0110\u0112\u0114\u0116\u0118\u011a\u011c\u011e\u0120\u0122\u0124\u0126\u0128\u012a\u012c\u012e\u0130\u0132\u0134\u0136\u0139\u013b\u013d\u013f\u0141\u0143\u0145\u0147\u014a\u014c\u014e\u0150\u0152\u0154\u0156\u0158\u015a\u015c\u015e\u0160\u0162\u0164\u0166\u0168\u016a\u016c\u016e\u0170\u0172\u0174\u0176\u0178-\u0179\u017b\u017d\u0181-\u0182\u0184\u0186-\u0187\u0189-\u018b\u018e-\u0191\u0193-\u0194\u0196-\u0198\u019c-\u019d\u019f-\u01a0\u01a2\u01a4\u01a6-\u01a7\u01a9\u01ac\u01ae-\u01af\u01b1-\u01b3\u01b5\u01b7-\u01b8\u01bc\u01c4\u01c7\u01ca\u01cd\u01cf\u01d1\u01d3\u01d5\u01d7\u01d9\u01db\u01de\u01e0\u01e2\u01e4\u01e6\u01e8\u01ea\u01ec\u01ee\u01f1\u01f4\u01f6-\u01f8\u01fa\u01fc\u01fe\u0200\u0202\u0204\u0206\u0208\u020a\u020c\u020e\u0210\u0212\u0214\u0216\u0218\u021a\u021c\u021e\u0220\u0222\u0224\u0226\u0228\u022a\u022c\u022e\u0230\u0232\u023a-\u023b\u023d-\u023e\u0241\u0243-\u0246\u0248\u024a\u024c\u024e\u0370\u0372\u0376\u037f\u0386\u0388-\u038a\u038c\u038e-\u038f\u0391-\u03a1\u03a3-\u03ab\u03cf\u03d2-\u03d4\u03d8\u03da\u03dc\u03de\u03e0\u03e2\u03e4\u03e6\u03e8\u03ea\u03ec\u03ee\u03f4\u03f7\u03f9-\u03fa\u03fd-\u042f\u0460\u0462\u0464\u0466\u0468\u046a\u046c\u046e\u0470\u0472\u0474\u0476\u0478\u047a\u047c\u047e\u0480\u048a\u048c\u048e\u0490\u0492\u0494\u0496\u0498\u049a\u049c\u049e\u04a0\u04a2\u04a4\u04a6\u04a8\u04aa\u04ac\u04ae\u04b0\u04b2\u04b4\u04b6\u04b8\u04ba\u04bc\u04be\u04c0-\u04c1\u04c3\u04c5\u04c7\u04c9\u04cb\u04cd\u04d0\u04d2\u04d4\u04d6\u04d8\u04da\u04dc\u04de\u04e0\u04e2\u04e4\u04e6\u04e8\u04ea\u04ec\u04ee\u04f0\u04f2\u04f4\u04f6\u04f8\u04fa\u04fc\u04fe\u0500\u0502\u0504\u0506\u0508\u050a\u050c\u050e\u0510\u0512\u0514\u0516\u0518\u051a\u051c\u051e\u0520\u0522\u0524\u0526\u0528\u052a\u052c\u052e\u0531-\u0556\u10a0-\u10c5\u10c7\u10cd\u13a0-\u13f5\u1c90-\u1cba\u1cbd-\u1cbf\u1e00\u1e02\u1e04\u1e06\u1e08\u1e0a\u1e0c\u1e0e\u1e10\u1e12\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1e1e\u1e20\u1e22\u1e24\u1e26\u1e28\u1e2a\u1e2c\u1e2e\u1e30\u1e32\u1e34\u1e36\u1e38\u1e3a\u1e3c\u1e3e\u1e40\u1e42\u1e44\u1e46\u1e48\u1e4a\u1e4c\u1e4e\u1e50\u1e52\u1e54\u1e56\u1e58\u1e5a\u1e5c\u1e5e\u1e60\u1e62\u1e64\u1e66\u1e68\u1e6a\u1e6c\u1e6e\u1e70\u1e72\u1e74\u1e76\u1e78\u1e7a\u1e7c\u1e7e\u1e80\u1e82\u1e84\u1e86\u1e88\u1e8a\u1e8c\u1e8e\u1e90\u1e92\u1e94\u1e9e\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6\u1ec8\u1eca\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0\u1ef2\u1ef4\u1ef6\u1ef8\u1efa\u1efc\u1efe\u1f08-\u1f0f\u1f18-\u1f1d\u1f28-\u1f2f\u1f38-\u1f3f\u1f48-\u1f4d\u1f59\u1f5b\u1f5d\u1f5f\u1f68-\u1f6f\u1fb8-\u1fbb\u1fc8-\u1fcb\u1fd8-\u1fdb\u1fe8-\u1fec\u1ff8-\u1ffb\u2102\u2107\u210b-\u210d\u2110-\u2112\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u2130-\u2133\u213e-\u213f\u2145\u2183\u2c00-\u2c2e\u2c60\u2c62-\u2c64\u2c67\u2c69\u2c6b\u2c6d-\u2c70\u2c72\u2c75\u2c7e-\u2c80\u2c82\u2c84\u2c86\u2c88\u2c8a\u2c8c\u2c8e\u2c90\u2c92\u2c94\u2c96\u2c98\u2c9a\u2c9c\u2c9e\u2ca0\u2ca2\u2ca4\u2ca6\u2ca8\u2caa\u2cac\u2cae\u2cb0\u2cb2\u2cb4\u2cb6\u2cb8\u2cba\u2cbc\u2cbe\u2cc0\u2cc2\u2cc4\u2cc6\u2cc8\u2cca\u2ccc\u2cce\u2cd0\u2cd2\u2cd4\u2cd6\u2cd8\u2cda\u2cdc\u2cde\u2ce0\u2ce2\u2ceb\u2ced\u2cf2\ua640\ua642\ua644\ua646\ua648\ua64a\ua64c\ua64e\ua650\ua652\ua654\ua656\ua658\ua65a\ua65c\ua65e\ua660\ua662\ua664\ua666\ua668\ua66a\ua66c\ua680\ua682\ua684\ua686\ua688\ua68a\ua68c\ua68e\ua690\ua692\ua694\ua696\ua698\ua69a\ua722\ua724\ua726\ua728\ua72a\ua72c\ua72e\ua732\ua734\ua736\ua738\ua73a\ua73c\ua73e\ua740\ua742\ua744\ua746\ua748\ua74a\ua74c\ua74e\ua750\ua752\ua754\ua756\ua758\ua75a\ua75c\ua75e\ua760\ua762\ua764\ua766\ua768\ua76a\ua76c\ua76e\ua779\ua77b\ua77d-\ua77e\ua780\ua782\ua784\ua786\ua78b\ua78d\ua790\ua792\ua796\ua798\ua79a\ua79c\ua79e\ua7a0\ua7a2\ua7a4\ua7a6\ua7a8\ua7aa-\ua7ae\ua7b0-\ua7b4\ua7b6\ua7b8\uff21-\uff3a\U00010400-\U00010427\U000104b0-\U000104d3\U00010c80-\U00010cb2\U000118a0-\U000118bf\U00016e40-\U00016e5f\U0001d400-\U0001d419\U0001d434-\U0001d44d\U0001d468-\U0001d481\U0001d49c\U0001d49e-\U0001d49f\U0001d4a2\U0001d4a5-\U0001d4a6\U0001d4a9-\U0001d4ac\U0001d4ae-\U0001d4b5\U0001d4d0-\U0001d4e9\U0001d504-\U0001d505\U0001d507-\U0001d50a\U0001d50d-\U0001d514\U0001d516-\U0001d51c\U0001d538-\U0001d539\U0001d53b-\U0001d53e\U0001d540-\U0001d544\U0001d546\U0001d54a-\U0001d550\U0001d56c-\U0001d585\U0001d5a0-\U0001d5b9\U0001d5d4-\U0001d5ed\U0001d608-\U0001d621\U0001d63c-\U0001d655\U0001d670-\U0001d689\U0001d6a8-\U0001d6c0\U0001d6e2-\U0001d6fa\U0001d71c-\U0001d734\U0001d756-\U0001d76e\U0001d790-\U0001d7a8\U0001d7ca\U0001e900-\U0001e921'
+
+Mc = '\u0903\u093b\u093e-\u0940\u0949-\u094c\u094e-\u094f\u0982-\u0983\u09be-\u09c0\u09c7-\u09c8\u09cb-\u09cc\u09d7\u0a03\u0a3e-\u0a40\u0a83\u0abe-\u0ac0\u0ac9\u0acb-\u0acc\u0b02-\u0b03\u0b3e\u0b40\u0b47-\u0b48\u0b4b-\u0b4c\u0b57\u0bbe-\u0bbf\u0bc1-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcc\u0bd7\u0c01-\u0c03\u0c41-\u0c44\u0c82-\u0c83\u0cbe\u0cc0-\u0cc4\u0cc7-\u0cc8\u0cca-\u0ccb\u0cd5-\u0cd6\u0d02-\u0d03\u0d3e-\u0d40\u0d46-\u0d48\u0d4a-\u0d4c\u0d57\u0d82-\u0d83\u0dcf-\u0dd1\u0dd8-\u0ddf\u0df2-\u0df3\u0f3e-\u0f3f\u0f7f\u102b-\u102c\u1031\u1038\u103b-\u103c\u1056-\u1057\u1062-\u1064\u1067-\u106d\u1083-\u1084\u1087-\u108c\u108f\u109a-\u109c\u17b6\u17be-\u17c5\u17c7-\u17c8\u1923-\u1926\u1929-\u192b\u1930-\u1931\u1933-\u1938\u1a19-\u1a1a\u1a55\u1a57\u1a61\u1a63-\u1a64\u1a6d-\u1a72\u1b04\u1b35\u1b3b\u1b3d-\u1b41\u1b43-\u1b44\u1b82\u1ba1\u1ba6-\u1ba7\u1baa\u1be7\u1bea-\u1bec\u1bee\u1bf2-\u1bf3\u1c24-\u1c2b\u1c34-\u1c35\u1ce1\u1cf2-\u1cf3\u1cf7\u302e-\u302f\ua823-\ua824\ua827\ua880-\ua881\ua8b4-\ua8c3\ua952-\ua953\ua983\ua9b4-\ua9b5\ua9ba-\ua9bb\ua9bd-\ua9c0\uaa2f-\uaa30\uaa33-\uaa34\uaa4d\uaa7b\uaa7d\uaaeb\uaaee-\uaaef\uaaf5\uabe3-\uabe4\uabe6-\uabe7\uabe9-\uabea\uabec\U00011000\U00011002\U00011082\U000110b0-\U000110b2\U000110b7-\U000110b8\U0001112c\U00011145-\U00011146\U00011182\U000111b3-\U000111b5\U000111bf-\U000111c0\U0001122c-\U0001122e\U00011232-\U00011233\U00011235\U000112e0-\U000112e2\U00011302-\U00011303\U0001133e-\U0001133f\U00011341-\U00011344\U00011347-\U00011348\U0001134b-\U0001134d\U00011357\U00011362-\U00011363\U00011435-\U00011437\U00011440-\U00011441\U00011445\U000114b0-\U000114b2\U000114b9\U000114bb-\U000114be\U000114c1\U000115af-\U000115b1\U000115b8-\U000115bb\U000115be\U00011630-\U00011632\U0001163b-\U0001163c\U0001163e\U000116ac\U000116ae-\U000116af\U000116b6\U00011720-\U00011721\U00011726\U0001182c-\U0001182e\U00011838\U00011a39\U00011a57-\U00011a58\U00011a97\U00011c2f\U00011c3e\U00011ca9\U00011cb1\U00011cb4\U00011d8a-\U00011d8e\U00011d93-\U00011d94\U00011d96\U00011ef5-\U00011ef6\U00016f51-\U00016f7e\U0001d165-\U0001d166\U0001d16d-\U0001d172'
+
+Me = '\u0488-\u0489\u1abe\u20dd-\u20e0\u20e2-\u20e4\ua670-\ua672'
+
+Mn = '\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05c7\u0610-\u061a\u064b-\u065f\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7-\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u07fd\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u08d3-\u08e1\u08e3-\u0902\u093a\u093c\u0941-\u0948\u094d\u0951-\u0957\u0962-\u0963\u0981\u09bc\u09c1-\u09c4\u09cd\u09e2-\u09e3\u09fe\u0a01-\u0a02\u0a3c\u0a41-\u0a42\u0a47-\u0a48\u0a4b-\u0a4d\u0a51\u0a70-\u0a71\u0a75\u0a81-\u0a82\u0abc\u0ac1-\u0ac5\u0ac7-\u0ac8\u0acd\u0ae2-\u0ae3\u0afa-\u0aff\u0b01\u0b3c\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b62-\u0b63\u0b82\u0bc0\u0bcd\u0c00\u0c04\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55-\u0c56\u0c62-\u0c63\u0c81\u0cbc\u0cbf\u0cc6\u0ccc-\u0ccd\u0ce2-\u0ce3\u0d00-\u0d01\u0d3b-\u0d3c\u0d41-\u0d44\u0d4d\u0d62-\u0d63\u0dca\u0dd2-\u0dd4\u0dd6\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb-\u0ebc\u0ec8-\u0ecd\u0f18-\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039-\u103a\u103d-\u103e\u1058-\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085-\u1086\u108d\u109d\u135d-\u135f\u1712-\u1714\u1732-\u1734\u1752-\u1753\u1772-\u1773\u17b4-\u17b5\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u1885-\u1886\u18a9\u1920-\u1922\u1927-\u1928\u1932\u1939-\u193b\u1a17-\u1a18\u1a1b\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1ab0-\u1abd\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80-\u1b81\u1ba2-\u1ba5\u1ba8-\u1ba9\u1bab-\u1bad\u1be6\u1be8-\u1be9\u1bed\u1bef-\u1bf1\u1c2c-\u1c33\u1c36-\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1cf4\u1cf8-\u1cf9\u1dc0-\u1df9\u1dfb-\u1dff\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302d\u3099-\u309a\ua66f\ua674-\ua67d\ua69e-\ua69f\ua6f0-\ua6f1\ua802\ua806\ua80b\ua825-\ua826\ua8c4-\ua8c5\ua8e0-\ua8f1\ua8ff\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\ua9e5\uaa29-\uaa2e\uaa31-\uaa32\uaa35-\uaa36\uaa43\uaa4c\uaa7c\uaab0\uaab2-\uaab4\uaab7-\uaab8\uaabe-\uaabf\uaac1\uaaec-\uaaed\uaaf6\uabe5\uabe8\uabed\ufb1e\ufe00-\ufe0f\ufe20-\ufe2f\U000101fd\U000102e0\U00010376-\U0001037a\U00010a01-\U00010a03\U00010a05-\U00010a06\U00010a0c-\U00010a0f\U00010a38-\U00010a3a\U00010a3f\U00010ae5-\U00010ae6\U00010d24-\U00010d27\U00010f46-\U00010f50\U00011001\U00011038-\U00011046\U0001107f-\U00011081\U000110b3-\U000110b6\U000110b9-\U000110ba\U00011100-\U00011102\U00011127-\U0001112b\U0001112d-\U00011134\U00011173\U00011180-\U00011181\U000111b6-\U000111be\U000111c9-\U000111cc\U0001122f-\U00011231\U00011234\U00011236-\U00011237\U0001123e\U000112df\U000112e3-\U000112ea\U00011300-\U00011301\U0001133b-\U0001133c\U00011340\U00011366-\U0001136c\U00011370-\U00011374\U00011438-\U0001143f\U00011442-\U00011444\U00011446\U0001145e\U000114b3-\U000114b8\U000114ba\U000114bf-\U000114c0\U000114c2-\U000114c3\U000115b2-\U000115b5\U000115bc-\U000115bd\U000115bf-\U000115c0\U000115dc-\U000115dd\U00011633-\U0001163a\U0001163d\U0001163f-\U00011640\U000116ab\U000116ad\U000116b0-\U000116b5\U000116b7\U0001171d-\U0001171f\U00011722-\U00011725\U00011727-\U0001172b\U0001182f-\U00011837\U00011839-\U0001183a\U00011a01-\U00011a0a\U00011a33-\U00011a38\U00011a3b-\U00011a3e\U00011a47\U00011a51-\U00011a56\U00011a59-\U00011a5b\U00011a8a-\U00011a96\U00011a98-\U00011a99\U00011c30-\U00011c36\U00011c38-\U00011c3d\U00011c3f\U00011c92-\U00011ca7\U00011caa-\U00011cb0\U00011cb2-\U00011cb3\U00011cb5-\U00011cb6\U00011d31-\U00011d36\U00011d3a\U00011d3c-\U00011d3d\U00011d3f-\U00011d45\U00011d47\U00011d90-\U00011d91\U00011d95\U00011d97\U00011ef3-\U00011ef4\U00016af0-\U00016af4\U00016b30-\U00016b36\U00016f8f-\U00016f92\U0001bc9d-\U0001bc9e\U0001d167-\U0001d169\U0001d17b-\U0001d182\U0001d185-\U0001d18b\U0001d1aa-\U0001d1ad\U0001d242-\U0001d244\U0001da00-\U0001da36\U0001da3b-\U0001da6c\U0001da75\U0001da84\U0001da9b-\U0001da9f\U0001daa1-\U0001daaf\U0001e000-\U0001e006\U0001e008-\U0001e018\U0001e01b-\U0001e021\U0001e023-\U0001e024\U0001e026-\U0001e02a\U0001e8d0-\U0001e8d6\U0001e944-\U0001e94a\U000e0100-\U000e01ef'
+
+Nd = '0-9\u0660-\u0669\u06f0-\u06f9\u07c0-\u07c9\u0966-\u096f\u09e6-\u09ef\u0a66-\u0a6f\u0ae6-\u0aef\u0b66-\u0b6f\u0be6-\u0bef\u0c66-\u0c6f\u0ce6-\u0cef\u0d66-\u0d6f\u0de6-\u0def\u0e50-\u0e59\u0ed0-\u0ed9\u0f20-\u0f29\u1040-\u1049\u1090-\u1099\u17e0-\u17e9\u1810-\u1819\u1946-\u194f\u19d0-\u19d9\u1a80-\u1a89\u1a90-\u1a99\u1b50-\u1b59\u1bb0-\u1bb9\u1c40-\u1c49\u1c50-\u1c59\ua620-\ua629\ua8d0-\ua8d9\ua900-\ua909\ua9d0-\ua9d9\ua9f0-\ua9f9\uaa50-\uaa59\uabf0-\uabf9\uff10-\uff19\U000104a0-\U000104a9\U00010d30-\U00010d39\U00011066-\U0001106f\U000110f0-\U000110f9\U00011136-\U0001113f\U000111d0-\U000111d9\U000112f0-\U000112f9\U00011450-\U00011459\U000114d0-\U000114d9\U00011650-\U00011659\U000116c0-\U000116c9\U00011730-\U00011739\U000118e0-\U000118e9\U00011c50-\U00011c59\U00011d50-\U00011d59\U00011da0-\U00011da9\U00016a60-\U00016a69\U00016b50-\U00016b59\U0001d7ce-\U0001d7ff\U0001e950-\U0001e959'
+
+Nl = '\u16ee-\u16f0\u2160-\u2182\u2185-\u2188\u3007\u3021-\u3029\u3038-\u303a\ua6e6-\ua6ef\U00010140-\U00010174\U00010341\U0001034a\U000103d1-\U000103d5\U00012400-\U0001246e'
+
+No = '\xb2-\xb3\xb9\xbc-\xbe\u09f4-\u09f9\u0b72-\u0b77\u0bf0-\u0bf2\u0c78-\u0c7e\u0d58-\u0d5e\u0d70-\u0d78\u0f2a-\u0f33\u1369-\u137c\u17f0-\u17f9\u19da\u2070\u2074-\u2079\u2080-\u2089\u2150-\u215f\u2189\u2460-\u249b\u24ea-\u24ff\u2776-\u2793\u2cfd\u3192-\u3195\u3220-\u3229\u3248-\u324f\u3251-\u325f\u3280-\u3289\u32b1-\u32bf\ua830-\ua835\U00010107-\U00010133\U00010175-\U00010178\U0001018a-\U0001018b\U000102e1-\U000102fb\U00010320-\U00010323\U00010858-\U0001085f\U00010879-\U0001087f\U000108a7-\U000108af\U000108fb-\U000108ff\U00010916-\U0001091b\U000109bc-\U000109bd\U000109c0-\U000109cf\U000109d2-\U000109ff\U00010a40-\U00010a48\U00010a7d-\U00010a7e\U00010a9d-\U00010a9f\U00010aeb-\U00010aef\U00010b58-\U00010b5f\U00010b78-\U00010b7f\U00010ba9-\U00010baf\U00010cfa-\U00010cff\U00010e60-\U00010e7e\U00010f1d-\U00010f26\U00010f51-\U00010f54\U00011052-\U00011065\U000111e1-\U000111f4\U0001173a-\U0001173b\U000118ea-\U000118f2\U00011c5a-\U00011c6c\U00016b5b-\U00016b61\U00016e80-\U00016e96\U0001d2e0-\U0001d2f3\U0001d360-\U0001d378\U0001e8c7-\U0001e8cf\U0001ec71-\U0001ecab\U0001ecad-\U0001ecaf\U0001ecb1-\U0001ecb4\U0001f100-\U0001f10c'
+
+Pc = '_\u203f-\u2040\u2054\ufe33-\ufe34\ufe4d-\ufe4f\uff3f'
+
+Pd = '\\-\u058a\u05be\u1400\u1806\u2010-\u2015\u2e17\u2e1a\u2e3a-\u2e3b\u2e40\u301c\u3030\u30a0\ufe31-\ufe32\ufe58\ufe63\uff0d'
+
+Pe = ')\\]}\u0f3b\u0f3d\u169c\u2046\u207e\u208e\u2309\u230b\u232a\u2769\u276b\u276d\u276f\u2771\u2773\u2775\u27c6\u27e7\u27e9\u27eb\u27ed\u27ef\u2984\u2986\u2988\u298a\u298c\u298e\u2990\u2992\u2994\u2996\u2998\u29d9\u29db\u29fd\u2e23\u2e25\u2e27\u2e29\u3009\u300b\u300d\u300f\u3011\u3015\u3017\u3019\u301b\u301e-\u301f\ufd3e\ufe18\ufe36\ufe38\ufe3a\ufe3c\ufe3e\ufe40\ufe42\ufe44\ufe48\ufe5a\ufe5c\ufe5e\uff09\uff3d\uff5d\uff60\uff63'
+
+Pf = '\xbb\u2019\u201d\u203a\u2e03\u2e05\u2e0a\u2e0d\u2e1d\u2e21'
+
+Pi = '\xab\u2018\u201b-\u201c\u201f\u2039\u2e02\u2e04\u2e09\u2e0c\u2e1c\u2e20'
+
+Po = "!-#%-'*,.-/:-;?-@\\\\\xa1\xa7\xb6-\xb7\xbf\u037e\u0387\u055a-\u055f\u0589\u05c0\u05c3\u05c6\u05f3-\u05f4\u0609-\u060a\u060c-\u060d\u061b\u061e-\u061f\u066a-\u066d\u06d4\u0700-\u070d\u07f7-\u07f9\u0830-\u083e\u085e\u0964-\u0965\u0970\u09fd\u0a76\u0af0\u0c84\u0df4\u0e4f\u0e5a-\u0e5b\u0f04-\u0f12\u0f14\u0f85\u0fd0-\u0fd4\u0fd9-\u0fda\u104a-\u104f\u10fb\u1360-\u1368\u166d-\u166e\u16eb-\u16ed\u1735-\u1736\u17d4-\u17d6\u17d8-\u17da\u1800-\u1805\u1807-\u180a\u1944-\u1945\u1a1e-\u1a1f\u1aa0-\u1aa6\u1aa8-\u1aad\u1b5a-\u1b60\u1bfc-\u1bff\u1c3b-\u1c3f\u1c7e-\u1c7f\u1cc0-\u1cc7\u1cd3\u2016-\u2017\u2020-\u2027\u2030-\u2038\u203b-\u203e\u2041-\u2043\u2047-\u2051\u2053\u2055-\u205e\u2cf9-\u2cfc\u2cfe-\u2cff\u2d70\u2e00-\u2e01\u2e06-\u2e08\u2e0b\u2e0e-\u2e16\u2e18-\u2e19\u2e1b\u2e1e-\u2e1f\u2e2a-\u2e2e\u2e30-\u2e39\u2e3c-\u2e3f\u2e41\u2e43-\u2e4e\u3001-\u3003\u303d\u30fb\ua4fe-\ua4ff\ua60d-\ua60f\ua673\ua67e\ua6f2-\ua6f7\ua874-\ua877\ua8ce-\ua8cf\ua8f8-\ua8fa\ua8fc\ua92e-\ua92f\ua95f\ua9c1-\ua9cd\ua9de-\ua9df\uaa5c-\uaa5f\uaade-\uaadf\uaaf0-\uaaf1\uabeb\ufe10-\ufe16\ufe19\ufe30\ufe45-\ufe46\ufe49-\ufe4c\ufe50-\ufe52\ufe54-\ufe57\ufe5f-\ufe61\ufe68\ufe6a-\ufe6b\uff01-\uff03\uff05-\uff07\uff0a\uff0c\uff0e-\uff0f\uff1a-\uff1b\uff1f-\uff20\uff3c\uff61\uff64-\uff65\U00010100-\U00010102\U0001039f\U000103d0\U0001056f\U00010857\U0001091f\U0001093f\U00010a50-\U00010a58\U00010a7f\U00010af0-\U00010af6\U00010b39-\U00010b3f\U00010b99-\U00010b9c\U00010f55-\U00010f59\U00011047-\U0001104d\U000110bb-\U000110bc\U000110be-\U000110c1\U00011140-\U00011143\U00011174-\U00011175\U000111c5-\U000111c8\U000111cd\U000111db\U000111dd-\U000111df\U00011238-\U0001123d\U000112a9\U0001144b-\U0001144f\U0001145b\U0001145d\U000114c6\U000115c1-\U000115d7\U00011641-\U00011643\U00011660-\U0001166c\U0001173c-\U0001173e\U0001183b\U00011a3f-\U00011a46\U00011a9a-\U00011a9c\U00011a9e-\U00011aa2\U00011c41-\U00011c45\U00011c70-\U00011c71\U00011ef7-\U00011ef8\U00012470-\U00012474\U00016a6e-\U00016a6f\U00016af5\U00016b37-\U00016b3b\U00016b44\U00016e97-\U00016e9a\U0001bc9f\U0001da87-\U0001da8b\U0001e95e-\U0001e95f"
+
+Ps = '(\\[{\u0f3a\u0f3c\u169b\u201a\u201e\u2045\u207d\u208d\u2308\u230a\u2329\u2768\u276a\u276c\u276e\u2770\u2772\u2774\u27c5\u27e6\u27e8\u27ea\u27ec\u27ee\u2983\u2985\u2987\u2989\u298b\u298d\u298f\u2991\u2993\u2995\u2997\u29d8\u29da\u29fc\u2e22\u2e24\u2e26\u2e28\u2e42\u3008\u300a\u300c\u300e\u3010\u3014\u3016\u3018\u301a\u301d\ufd3f\ufe17\ufe35\ufe37\ufe39\ufe3b\ufe3d\ufe3f\ufe41\ufe43\ufe47\ufe59\ufe5b\ufe5d\uff08\uff3b\uff5b\uff5f\uff62'
+
+Sc = '$\xa2-\xa5\u058f\u060b\u07fe-\u07ff\u09f2-\u09f3\u09fb\u0af1\u0bf9\u0e3f\u17db\u20a0-\u20bf\ua838\ufdfc\ufe69\uff04\uffe0-\uffe1\uffe5-\uffe6\U0001ecb0'
+
+Sk = '\\^`\xa8\xaf\xb4\xb8\u02c2-\u02c5\u02d2-\u02df\u02e5-\u02eb\u02ed\u02ef-\u02ff\u0375\u0384-\u0385\u1fbd\u1fbf-\u1fc1\u1fcd-\u1fcf\u1fdd-\u1fdf\u1fed-\u1fef\u1ffd-\u1ffe\u309b-\u309c\ua700-\ua716\ua720-\ua721\ua789-\ua78a\uab5b\ufbb2-\ufbc1\uff3e\uff40\uffe3\U0001f3fb-\U0001f3ff'
+
+Sm = '+<->|~\xac\xb1\xd7\xf7\u03f6\u0606-\u0608\u2044\u2052\u207a-\u207c\u208a-\u208c\u2118\u2140-\u2144\u214b\u2190-\u2194\u219a-\u219b\u21a0\u21a3\u21a6\u21ae\u21ce-\u21cf\u21d2\u21d4\u21f4-\u22ff\u2320-\u2321\u237c\u239b-\u23b3\u23dc-\u23e1\u25b7\u25c1\u25f8-\u25ff\u266f\u27c0-\u27c4\u27c7-\u27e5\u27f0-\u27ff\u2900-\u2982\u2999-\u29d7\u29dc-\u29fb\u29fe-\u2aff\u2b30-\u2b44\u2b47-\u2b4c\ufb29\ufe62\ufe64-\ufe66\uff0b\uff1c-\uff1e\uff5c\uff5e\uffe2\uffe9-\uffec\U0001d6c1\U0001d6db\U0001d6fb\U0001d715\U0001d735\U0001d74f\U0001d76f\U0001d789\U0001d7a9\U0001d7c3\U0001eef0-\U0001eef1'
+
+So = '\xa6\xa9\xae\xb0\u0482\u058d-\u058e\u060e-\u060f\u06de\u06e9\u06fd-\u06fe\u07f6\u09fa\u0b70\u0bf3-\u0bf8\u0bfa\u0c7f\u0d4f\u0d79\u0f01-\u0f03\u0f13\u0f15-\u0f17\u0f1a-\u0f1f\u0f34\u0f36\u0f38\u0fbe-\u0fc5\u0fc7-\u0fcc\u0fce-\u0fcf\u0fd5-\u0fd8\u109e-\u109f\u1390-\u1399\u1940\u19de-\u19ff\u1b61-\u1b6a\u1b74-\u1b7c\u2100-\u2101\u2103-\u2106\u2108-\u2109\u2114\u2116-\u2117\u211e-\u2123\u2125\u2127\u2129\u212e\u213a-\u213b\u214a\u214c-\u214d\u214f\u218a-\u218b\u2195-\u2199\u219c-\u219f\u21a1-\u21a2\u21a4-\u21a5\u21a7-\u21ad\u21af-\u21cd\u21d0-\u21d1\u21d3\u21d5-\u21f3\u2300-\u2307\u230c-\u231f\u2322-\u2328\u232b-\u237b\u237d-\u239a\u23b4-\u23db\u23e2-\u2426\u2440-\u244a\u249c-\u24e9\u2500-\u25b6\u25b8-\u25c0\u25c2-\u25f7\u2600-\u266e\u2670-\u2767\u2794-\u27bf\u2800-\u28ff\u2b00-\u2b2f\u2b45-\u2b46\u2b4d-\u2b73\u2b76-\u2b95\u2b98-\u2bc8\u2bca-\u2bfe\u2ce5-\u2cea\u2e80-\u2e99\u2e9b-\u2ef3\u2f00-\u2fd5\u2ff0-\u2ffb\u3004\u3012-\u3013\u3020\u3036-\u3037\u303e-\u303f\u3190-\u3191\u3196-\u319f\u31c0-\u31e3\u3200-\u321e\u322a-\u3247\u3250\u3260-\u327f\u328a-\u32b0\u32c0-\u32fe\u3300-\u33ff\u4dc0-\u4dff\ua490-\ua4c6\ua828-\ua82b\ua836-\ua837\ua839\uaa77-\uaa79\ufdfd\uffe4\uffe8\uffed-\uffee\ufffc-\ufffd\U00010137-\U0001013f\U00010179-\U00010189\U0001018c-\U0001018e\U00010190-\U0001019b\U000101a0\U000101d0-\U000101fc\U00010877-\U00010878\U00010ac8\U0001173f\U00016b3c-\U00016b3f\U00016b45\U0001bc9c\U0001d000-\U0001d0f5\U0001d100-\U0001d126\U0001d129-\U0001d164\U0001d16a-\U0001d16c\U0001d183-\U0001d184\U0001d18c-\U0001d1a9\U0001d1ae-\U0001d1e8\U0001d200-\U0001d241\U0001d245\U0001d300-\U0001d356\U0001d800-\U0001d9ff\U0001da37-\U0001da3a\U0001da6d-\U0001da74\U0001da76-\U0001da83\U0001da85-\U0001da86\U0001ecac\U0001f000-\U0001f02b\U0001f030-\U0001f093\U0001f0a0-\U0001f0ae\U0001f0b1-\U0001f0bf\U0001f0c1-\U0001f0cf\U0001f0d1-\U0001f0f5\U0001f110-\U0001f16b\U0001f170-\U0001f1ac\U0001f1e6-\U0001f202\U0001f210-\U0001f23b\U0001f240-\U0001f248\U0001f250-\U0001f251\U0001f260-\U0001f265\U0001f300-\U0001f3fa\U0001f400-\U0001f6d4\U0001f6e0-\U0001f6ec\U0001f6f0-\U0001f6f9\U0001f700-\U0001f773\U0001f780-\U0001f7d8\U0001f800-\U0001f80b\U0001f810-\U0001f847\U0001f850-\U0001f859\U0001f860-\U0001f887\U0001f890-\U0001f8ad\U0001f900-\U0001f90b\U0001f910-\U0001f93e\U0001f940-\U0001f970\U0001f973-\U0001f976\U0001f97a\U0001f97c-\U0001f9a2\U0001f9b0-\U0001f9b9\U0001f9c0-\U0001f9c2\U0001f9d0-\U0001f9ff\U0001fa60-\U0001fa6d'
+
+Zl = '\u2028'
+
+Zp = '\u2029'
+
+Zs = ' \xa0\u1680\u2000-\u200a\u202f\u205f\u3000'
+
+xid_continue = '0-9A-Z_a-z\xaa\xb5\xb7\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0300-\u0374\u0376-\u0377\u037b-\u037d\u037f\u0386-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u0483-\u0487\u048a-\u052f\u0531-\u0556\u0559\u0560-\u0588\u0591-\u05bd\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05c7\u05d0-\u05ea\u05ef-\u05f2\u0610-\u061a\u0620-\u0669\u066e-\u06d3\u06d5-\u06dc\u06df-\u06e8\u06ea-\u06fc\u06ff\u0710-\u074a\u074d-\u07b1\u07c0-\u07f5\u07fa\u07fd\u0800-\u082d\u0840-\u085b\u0860-\u086a\u08a0-\u08b4\u08b6-\u08bd\u08d3-\u08e1\u08e3-\u0963\u0966-\u096f\u0971-\u0983\u0985-\u098c\u098f-\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bc-\u09c4\u09c7-\u09c8\u09cb-\u09ce\u09d7\u09dc-\u09dd\u09df-\u09e3\u09e6-\u09f1\u09fc\u09fe\u0a01-\u0a03\u0a05-\u0a0a\u0a0f-\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32-\u0a33\u0a35-\u0a36\u0a38-\u0a39\u0a3c\u0a3e-\u0a42\u0a47-\u0a48\u0a4b-\u0a4d\u0a51\u0a59-\u0a5c\u0a5e\u0a66-\u0a75\u0a81-\u0a83\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2-\u0ab3\u0ab5-\u0ab9\u0abc-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ad0\u0ae0-\u0ae3\u0ae6-\u0aef\u0af9-\u0aff\u0b01-\u0b03\u0b05-\u0b0c\u0b0f-\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32-\u0b33\u0b35-\u0b39\u0b3c-\u0b44\u0b47-\u0b48\u0b4b-\u0b4d\u0b56-\u0b57\u0b5c-\u0b5d\u0b5f-\u0b63\u0b66-\u0b6f\u0b71\u0b82-\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99-\u0b9a\u0b9c\u0b9e-\u0b9f\u0ba3-\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd0\u0bd7\u0be6-\u0bef\u0c00-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55-\u0c56\u0c58-\u0c5a\u0c60-\u0c63\u0c66-\u0c6f\u0c80-\u0c83\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbc-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5-\u0cd6\u0cde\u0ce0-\u0ce3\u0ce6-\u0cef\u0cf1-\u0cf2\u0d00-\u0d03\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d44\u0d46-\u0d48\u0d4a-\u0d4e\u0d54-\u0d57\u0d5f-\u0d63\u0d66-\u0d6f\u0d7a-\u0d7f\u0d82-\u0d83\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2-\u0df3\u0e01-\u0e3a\u0e40-\u0e4e\u0e50-\u0e59\u0e81-\u0e82\u0e84\u0e87-\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa-\u0eab\u0ead-\u0eb9\u0ebb-\u0ebd\u0ec0-\u0ec4\u0ec6\u0ec8-\u0ecd\u0ed0-\u0ed9\u0edc-\u0edf\u0f00\u0f18-\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e-\u0f47\u0f49-\u0f6c\u0f71-\u0f84\u0f86-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1049\u1050-\u109d\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u135d-\u135f\u1369-\u1371\u1380-\u138f\u13a0-\u13f5\u13f8-\u13fd\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u170c\u170e-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176c\u176e-\u1770\u1772-\u1773\u1780-\u17d3\u17d7\u17dc-\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1820-\u1878\u1880-\u18aa\u18b0-\u18f5\u1900-\u191e\u1920-\u192b\u1930-\u193b\u1946-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u19d0-\u19da\u1a00-\u1a1b\u1a20-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1aa7\u1ab0-\u1abd\u1b00-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1bf3\u1c00-\u1c37\u1c40-\u1c49\u1c4d-\u1c7d\u1c80-\u1c88\u1c90-\u1cba\u1cbd-\u1cbf\u1cd0-\u1cd2\u1cd4-\u1cf9\u1d00-\u1df9\u1dfb-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u203f-\u2040\u2054\u2071\u207f\u2090-\u209c\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d7f-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2de0-\u2dff\u3005-\u3007\u3021-\u302f\u3031-\u3035\u3038-\u303c\u3041-\u3096\u3099-\u309a\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312f\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fef\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua62b\ua640-\ua66f\ua674-\ua67d\ua67f-\ua6f1\ua717-\ua71f\ua722-\ua788\ua78b-\ua7b9\ua7f7-\ua827\ua840-\ua873\ua880-\ua8c5\ua8d0-\ua8d9\ua8e0-\ua8f7\ua8fb\ua8fd-\ua92d\ua930-\ua953\ua960-\ua97c\ua980-\ua9c0\ua9cf-\ua9d9\ua9e0-\ua9fe\uaa00-\uaa36\uaa40-\uaa4d\uaa50-\uaa59\uaa60-\uaa76\uaa7a-\uaac2\uaadb-\uaadd\uaae0-\uaaef\uaaf2-\uaaf6\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab65\uab70-\uabea\uabec-\uabed\uabf0-\uabf9\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufbb1\ufbd3-\ufc5d\ufc64-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdf9\ufe00-\ufe0f\ufe20-\ufe2f\ufe33-\ufe34\ufe4d-\ufe4f\ufe71\ufe73\ufe77\ufe79\ufe7b\ufe7d\ufe7f-\ufefc\uff10-\uff19\uff21-\uff3a\uff3f\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc\U00010000-\U0001000b\U0001000d-\U00010026\U00010028-\U0001003a\U0001003c-\U0001003d\U0001003f-\U0001004d\U00010050-\U0001005d\U00010080-\U000100fa\U00010140-\U00010174\U000101fd\U00010280-\U0001029c\U000102a0-\U000102d0\U000102e0\U00010300-\U0001031f\U0001032d-\U0001034a\U00010350-\U0001037a\U00010380-\U0001039d\U000103a0-\U000103c3\U000103c8-\U000103cf\U000103d1-\U000103d5\U00010400-\U0001049d\U000104a0-\U000104a9\U000104b0-\U000104d3\U000104d8-\U000104fb\U00010500-\U00010527\U00010530-\U00010563\U00010600-\U00010736\U00010740-\U00010755\U00010760-\U00010767\U00010800-\U00010805\U00010808\U0001080a-\U00010835\U00010837-\U00010838\U0001083c\U0001083f-\U00010855\U00010860-\U00010876\U00010880-\U0001089e\U000108e0-\U000108f2\U000108f4-\U000108f5\U00010900-\U00010915\U00010920-\U00010939\U00010980-\U000109b7\U000109be-\U000109bf\U00010a00-\U00010a03\U00010a05-\U00010a06\U00010a0c-\U00010a13\U00010a15-\U00010a17\U00010a19-\U00010a35\U00010a38-\U00010a3a\U00010a3f\U00010a60-\U00010a7c\U00010a80-\U00010a9c\U00010ac0-\U00010ac7\U00010ac9-\U00010ae6\U00010b00-\U00010b35\U00010b40-\U00010b55\U00010b60-\U00010b72\U00010b80-\U00010b91\U00010c00-\U00010c48\U00010c80-\U00010cb2\U00010cc0-\U00010cf2\U00010d00-\U00010d27\U00010d30-\U00010d39\U00010f00-\U00010f1c\U00010f27\U00010f30-\U00010f50\U00011000-\U00011046\U00011066-\U0001106f\U0001107f-\U000110ba\U000110d0-\U000110e8\U000110f0-\U000110f9\U00011100-\U00011134\U00011136-\U0001113f\U00011144-\U00011146\U00011150-\U00011173\U00011176\U00011180-\U000111c4\U000111c9-\U000111cc\U000111d0-\U000111da\U000111dc\U00011200-\U00011211\U00011213-\U00011237\U0001123e\U00011280-\U00011286\U00011288\U0001128a-\U0001128d\U0001128f-\U0001129d\U0001129f-\U000112a8\U000112b0-\U000112ea\U000112f0-\U000112f9\U00011300-\U00011303\U00011305-\U0001130c\U0001130f-\U00011310\U00011313-\U00011328\U0001132a-\U00011330\U00011332-\U00011333\U00011335-\U00011339\U0001133b-\U00011344\U00011347-\U00011348\U0001134b-\U0001134d\U00011350\U00011357\U0001135d-\U00011363\U00011366-\U0001136c\U00011370-\U00011374\U00011400-\U0001144a\U00011450-\U00011459\U0001145e\U00011480-\U000114c5\U000114c7\U000114d0-\U000114d9\U00011580-\U000115b5\U000115b8-\U000115c0\U000115d8-\U000115dd\U00011600-\U00011640\U00011644\U00011650-\U00011659\U00011680-\U000116b7\U000116c0-\U000116c9\U00011700-\U0001171a\U0001171d-\U0001172b\U00011730-\U00011739\U00011800-\U0001183a\U000118a0-\U000118e9\U000118ff\U00011a00-\U00011a3e\U00011a47\U00011a50-\U00011a83\U00011a86-\U00011a99\U00011a9d\U00011ac0-\U00011af8\U00011c00-\U00011c08\U00011c0a-\U00011c36\U00011c38-\U00011c40\U00011c50-\U00011c59\U00011c72-\U00011c8f\U00011c92-\U00011ca7\U00011ca9-\U00011cb6\U00011d00-\U00011d06\U00011d08-\U00011d09\U00011d0b-\U00011d36\U00011d3a\U00011d3c-\U00011d3d\U00011d3f-\U00011d47\U00011d50-\U00011d59\U00011d60-\U00011d65\U00011d67-\U00011d68\U00011d6a-\U00011d8e\U00011d90-\U00011d91\U00011d93-\U00011d98\U00011da0-\U00011da9\U00011ee0-\U00011ef6\U00012000-\U00012399\U00012400-\U0001246e\U00012480-\U00012543\U00013000-\U0001342e\U00014400-\U00014646\U00016800-\U00016a38\U00016a40-\U00016a5e\U00016a60-\U00016a69\U00016ad0-\U00016aed\U00016af0-\U00016af4\U00016b00-\U00016b36\U00016b40-\U00016b43\U00016b50-\U00016b59\U00016b63-\U00016b77\U00016b7d-\U00016b8f\U00016e40-\U00016e7f\U00016f00-\U00016f44\U00016f50-\U00016f7e\U00016f8f-\U00016f9f\U00016fe0-\U00016fe1\U00017000-\U000187f1\U00018800-\U00018af2\U0001b000-\U0001b11e\U0001b170-\U0001b2fb\U0001bc00-\U0001bc6a\U0001bc70-\U0001bc7c\U0001bc80-\U0001bc88\U0001bc90-\U0001bc99\U0001bc9d-\U0001bc9e\U0001d165-\U0001d169\U0001d16d-\U0001d172\U0001d17b-\U0001d182\U0001d185-\U0001d18b\U0001d1aa-\U0001d1ad\U0001d242-\U0001d244\U0001d400-\U0001d454\U0001d456-\U0001d49c\U0001d49e-\U0001d49f\U0001d4a2\U0001d4a5-\U0001d4a6\U0001d4a9-\U0001d4ac\U0001d4ae-\U0001d4b9\U0001d4bb\U0001d4bd-\U0001d4c3\U0001d4c5-\U0001d505\U0001d507-\U0001d50a\U0001d50d-\U0001d514\U0001d516-\U0001d51c\U0001d51e-\U0001d539\U0001d53b-\U0001d53e\U0001d540-\U0001d544\U0001d546\U0001d54a-\U0001d550\U0001d552-\U0001d6a5\U0001d6a8-\U0001d6c0\U0001d6c2-\U0001d6da\U0001d6dc-\U0001d6fa\U0001d6fc-\U0001d714\U0001d716-\U0001d734\U0001d736-\U0001d74e\U0001d750-\U0001d76e\U0001d770-\U0001d788\U0001d78a-\U0001d7a8\U0001d7aa-\U0001d7c2\U0001d7c4-\U0001d7cb\U0001d7ce-\U0001d7ff\U0001da00-\U0001da36\U0001da3b-\U0001da6c\U0001da75\U0001da84\U0001da9b-\U0001da9f\U0001daa1-\U0001daaf\U0001e000-\U0001e006\U0001e008-\U0001e018\U0001e01b-\U0001e021\U0001e023-\U0001e024\U0001e026-\U0001e02a\U0001e800-\U0001e8c4\U0001e8d0-\U0001e8d6\U0001e900-\U0001e94a\U0001e950-\U0001e959\U0001ee00-\U0001ee03\U0001ee05-\U0001ee1f\U0001ee21-\U0001ee22\U0001ee24\U0001ee27\U0001ee29-\U0001ee32\U0001ee34-\U0001ee37\U0001ee39\U0001ee3b\U0001ee42\U0001ee47\U0001ee49\U0001ee4b\U0001ee4d-\U0001ee4f\U0001ee51-\U0001ee52\U0001ee54\U0001ee57\U0001ee59\U0001ee5b\U0001ee5d\U0001ee5f\U0001ee61-\U0001ee62\U0001ee64\U0001ee67-\U0001ee6a\U0001ee6c-\U0001ee72\U0001ee74-\U0001ee77\U0001ee79-\U0001ee7c\U0001ee7e\U0001ee80-\U0001ee89\U0001ee8b-\U0001ee9b\U0001eea1-\U0001eea3\U0001eea5-\U0001eea9\U0001eeab-\U0001eebb\U00020000-\U0002a6d6\U0002a700-\U0002b734\U0002b740-\U0002b81d\U0002b820-\U0002cea1\U0002ceb0-\U0002ebe0\U0002f800-\U0002fa1d\U000e0100-\U000e01ef'
+
+xid_start = 'A-Z_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376-\u0377\u037b-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0560-\u0588\u05d0-\u05ea\u05ef-\u05f2\u0620-\u064a\u066e-\u066f\u0671-\u06d3\u06d5\u06e5-\u06e6\u06ee-\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4-\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u0860-\u086a\u08a0-\u08b4\u08b6-\u08bd\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f-\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc-\u09dd\u09df-\u09e1\u09f0-\u09f1\u09fc\u0a05-\u0a0a\u0a0f-\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32-\u0a33\u0a35-\u0a36\u0a38-\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2-\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0-\u0ae1\u0af9\u0b05-\u0b0c\u0b0f-\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32-\u0b33\u0b35-\u0b39\u0b3d\u0b5c-\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99-\u0b9a\u0b9c\u0b9e-\u0b9f\u0ba3-\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58-\u0c5a\u0c60-\u0c61\u0c80\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0-\u0ce1\u0cf1-\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d54-\u0d56\u0d5f-\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e40-\u0e46\u0e81-\u0e82\u0e84\u0e87-\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa-\u0eab\u0ead-\u0eb0\u0eb2\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065-\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f5\u13f8-\u13fd\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1878\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae-\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1c80-\u1c88\u1c90-\u1cba\u1cbd-\u1cbf\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5-\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2-\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312f\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fef\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a-\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua7b9\ua7f7-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua8fd-\ua8fe\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5-\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab65\uab70-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufbb1\ufbd3-\ufc5d\ufc64-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdf9\ufe71\ufe73\ufe77\ufe79\ufe7b\ufe7d\ufe7f-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uff9d\uffa0-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc\U00010000-\U0001000b\U0001000d-\U00010026\U00010028-\U0001003a\U0001003c-\U0001003d\U0001003f-\U0001004d\U00010050-\U0001005d\U00010080-\U000100fa\U00010140-\U00010174\U00010280-\U0001029c\U000102a0-\U000102d0\U00010300-\U0001031f\U0001032d-\U0001034a\U00010350-\U00010375\U00010380-\U0001039d\U000103a0-\U000103c3\U000103c8-\U000103cf\U000103d1-\U000103d5\U00010400-\U0001049d\U000104b0-\U000104d3\U000104d8-\U000104fb\U00010500-\U00010527\U00010530-\U00010563\U00010600-\U00010736\U00010740-\U00010755\U00010760-\U00010767\U00010800-\U00010805\U00010808\U0001080a-\U00010835\U00010837-\U00010838\U0001083c\U0001083f-\U00010855\U00010860-\U00010876\U00010880-\U0001089e\U000108e0-\U000108f2\U000108f4-\U000108f5\U00010900-\U00010915\U00010920-\U00010939\U00010980-\U000109b7\U000109be-\U000109bf\U00010a00\U00010a10-\U00010a13\U00010a15-\U00010a17\U00010a19-\U00010a35\U00010a60-\U00010a7c\U00010a80-\U00010a9c\U00010ac0-\U00010ac7\U00010ac9-\U00010ae4\U00010b00-\U00010b35\U00010b40-\U00010b55\U00010b60-\U00010b72\U00010b80-\U00010b91\U00010c00-\U00010c48\U00010c80-\U00010cb2\U00010cc0-\U00010cf2\U00010d00-\U00010d23\U00010f00-\U00010f1c\U00010f27\U00010f30-\U00010f45\U00011003-\U00011037\U00011083-\U000110af\U000110d0-\U000110e8\U00011103-\U00011126\U00011144\U00011150-\U00011172\U00011176\U00011183-\U000111b2\U000111c1-\U000111c4\U000111da\U000111dc\U00011200-\U00011211\U00011213-\U0001122b\U00011280-\U00011286\U00011288\U0001128a-\U0001128d\U0001128f-\U0001129d\U0001129f-\U000112a8\U000112b0-\U000112de\U00011305-\U0001130c\U0001130f-\U00011310\U00011313-\U00011328\U0001132a-\U00011330\U00011332-\U00011333\U00011335-\U00011339\U0001133d\U00011350\U0001135d-\U00011361\U00011400-\U00011434\U00011447-\U0001144a\U00011480-\U000114af\U000114c4-\U000114c5\U000114c7\U00011580-\U000115ae\U000115d8-\U000115db\U00011600-\U0001162f\U00011644\U00011680-\U000116aa\U00011700-\U0001171a\U00011800-\U0001182b\U000118a0-\U000118df\U000118ff\U00011a00\U00011a0b-\U00011a32\U00011a3a\U00011a50\U00011a5c-\U00011a83\U00011a86-\U00011a89\U00011a9d\U00011ac0-\U00011af8\U00011c00-\U00011c08\U00011c0a-\U00011c2e\U00011c40\U00011c72-\U00011c8f\U00011d00-\U00011d06\U00011d08-\U00011d09\U00011d0b-\U00011d30\U00011d46\U00011d60-\U00011d65\U00011d67-\U00011d68\U00011d6a-\U00011d89\U00011d98\U00011ee0-\U00011ef2\U00012000-\U00012399\U00012400-\U0001246e\U00012480-\U00012543\U00013000-\U0001342e\U00014400-\U00014646\U00016800-\U00016a38\U00016a40-\U00016a5e\U00016ad0-\U00016aed\U00016b00-\U00016b2f\U00016b40-\U00016b43\U00016b63-\U00016b77\U00016b7d-\U00016b8f\U00016e40-\U00016e7f\U00016f00-\U00016f44\U00016f50\U00016f93-\U00016f9f\U00016fe0-\U00016fe1\U00017000-\U000187f1\U00018800-\U00018af2\U0001b000-\U0001b11e\U0001b170-\U0001b2fb\U0001bc00-\U0001bc6a\U0001bc70-\U0001bc7c\U0001bc80-\U0001bc88\U0001bc90-\U0001bc99\U0001d400-\U0001d454\U0001d456-\U0001d49c\U0001d49e-\U0001d49f\U0001d4a2\U0001d4a5-\U0001d4a6\U0001d4a9-\U0001d4ac\U0001d4ae-\U0001d4b9\U0001d4bb\U0001d4bd-\U0001d4c3\U0001d4c5-\U0001d505\U0001d507-\U0001d50a\U0001d50d-\U0001d514\U0001d516-\U0001d51c\U0001d51e-\U0001d539\U0001d53b-\U0001d53e\U0001d540-\U0001d544\U0001d546\U0001d54a-\U0001d550\U0001d552-\U0001d6a5\U0001d6a8-\U0001d6c0\U0001d6c2-\U0001d6da\U0001d6dc-\U0001d6fa\U0001d6fc-\U0001d714\U0001d716-\U0001d734\U0001d736-\U0001d74e\U0001d750-\U0001d76e\U0001d770-\U0001d788\U0001d78a-\U0001d7a8\U0001d7aa-\U0001d7c2\U0001d7c4-\U0001d7cb\U0001e800-\U0001e8c4\U0001e900-\U0001e943\U0001ee00-\U0001ee03\U0001ee05-\U0001ee1f\U0001ee21-\U0001ee22\U0001ee24\U0001ee27\U0001ee29-\U0001ee32\U0001ee34-\U0001ee37\U0001ee39\U0001ee3b\U0001ee42\U0001ee47\U0001ee49\U0001ee4b\U0001ee4d-\U0001ee4f\U0001ee51-\U0001ee52\U0001ee54\U0001ee57\U0001ee59\U0001ee5b\U0001ee5d\U0001ee5f\U0001ee61-\U0001ee62\U0001ee64\U0001ee67-\U0001ee6a\U0001ee6c-\U0001ee72\U0001ee74-\U0001ee77\U0001ee79-\U0001ee7c\U0001ee7e\U0001ee80-\U0001ee89\U0001ee8b-\U0001ee9b\U0001eea1-\U0001eea3\U0001eea5-\U0001eea9\U0001eeab-\U0001eebb\U00020000-\U0002a6d6\U0002a700-\U0002b734\U0002b740-\U0002b81d\U0002b820-\U0002cea1\U0002ceb0-\U0002ebe0\U0002f800-\U0002fa1d'
+
+cats = ['Cc', 'Cf', 'Cn', 'Co', 'Cs', 'Ll', 'Lm', 'Lo', 'Lt', 'Lu', 'Mc', 'Me', 'Mn', 'Nd', 'Nl', 'No', 'Pc', 'Pd', 'Pe', 'Pf', 'Pi', 'Po', 'Ps', 'Sc', 'Sk', 'Sm', 'So', 'Zl', 'Zp', 'Zs']
+
+# Generated from unidata 11.0.0
+
+def combine(*args):
+ return ''.join(globals()[cat] for cat in args)
+
+
+def allexcept(*args):
+ newcats = cats[:]
+ for arg in args:
+ newcats.remove(arg)
+ return ''.join(globals()[cat] for cat in newcats)
+
+
+def _handle_runs(char_list): # pragma: no cover
+ buf = []
+ for c in char_list:
+ if len(c) == 1:
+ if buf and buf[-1][1] == chr(ord(c)-1):
+ buf[-1] = (buf[-1][0], c)
+ else:
+ buf.append((c, c))
+ else:
+ buf.append((c, c))
+ for a, b in buf:
+ if a == b:
+ yield a
+ else:
+ yield '%s-%s' % (a, b)
+
+
+if __name__ == '__main__': # pragma: no cover
+ import unicodedata
+
+ categories = {'xid_start': [], 'xid_continue': []}
+
+ with open(__file__) as fp:
+ content = fp.read()
+
+ header = content[:content.find('Cc =')]
+ footer = content[content.find("def combine("):]
+
+ for code in range(0x110000):
+ c = chr(code)
+ cat = unicodedata.category(c)
+ if ord(c) == 0xdc00:
+ # Hack to avoid combining this combining with the preceding high
+ # surrogate, 0xdbff, when doing a repr.
+ c = '\\' + c
+ elif ord(c) in (0x2d, 0x5b, 0x5c, 0x5d, 0x5e):
+ # Escape regex metachars.
+ c = '\\' + c
+ categories.setdefault(cat, []).append(c)
+ # XID_START and XID_CONTINUE are special categories used for matching
+ # identifiers in Python 3.
+ if c.isidentifier():
+ categories['xid_start'].append(c)
+ if ('a' + c).isidentifier():
+ categories['xid_continue'].append(c)
+
+ with open(__file__, 'w') as fp:
+ fp.write(header)
+
+ for cat in sorted(categories):
+ val = ''.join(_handle_runs(categories[cat]))
+ fp.write('%s = %a\n\n' % (cat, val))
+
+ cats = sorted(categories)
+ cats.remove('xid_start')
+ cats.remove('xid_continue')
+ fp.write('cats = %r\n\n' % cats)
+
+ fp.write('# Generated from unidata %s\n\n' % (unicodedata.unidata_version,))
+
+ fp.write(footer)
diff --git a/src/pip/_vendor/pygments/util.py b/src/pip/_vendor/pygments/util.py
new file mode 100644
index 000000000..8032962dc
--- /dev/null
+++ b/src/pip/_vendor/pygments/util.py
@@ -0,0 +1,308 @@
+"""
+ pygments.util
+ ~~~~~~~~~~~~~
+
+ Utility functions.
+
+ :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import re
+from io import TextIOWrapper
+
+
+split_path_re = re.compile(r'[/\\ ]')
+doctype_lookup_re = re.compile(r'''
+ <!DOCTYPE\s+(
+ [a-zA-Z_][a-zA-Z0-9]*
+ (?: \s+ # optional in HTML5
+ [a-zA-Z_][a-zA-Z0-9]*\s+
+ "[^"]*")?
+ )
+ [^>]*>
+''', re.DOTALL | re.MULTILINE | re.VERBOSE)
+tag_re = re.compile(r'<(.+?)(\s.*?)?>.*?</.+?>',
+ re.IGNORECASE | re.DOTALL | re.MULTILINE)
+xml_decl_re = re.compile(r'\s*<\?xml[^>]*\?>', re.I)
+
+
+class ClassNotFound(ValueError):
+ """Raised if one of the lookup functions didn't find a matching class."""
+
+
+class OptionError(Exception):
+ pass
+
+
+def get_choice_opt(options, optname, allowed, default=None, normcase=False):
+ string = options.get(optname, default)
+ if normcase:
+ string = string.lower()
+ if string not in allowed:
+ raise OptionError('Value for option %s must be one of %s' %
+ (optname, ', '.join(map(str, allowed))))
+ return string
+
+
+def get_bool_opt(options, optname, default=None):
+ string = options.get(optname, default)
+ if isinstance(string, bool):
+ return string
+ elif isinstance(string, int):
+ return bool(string)
+ elif not isinstance(string, str):
+ raise OptionError('Invalid type %r for option %s; use '
+ '1/0, yes/no, true/false, on/off' % (
+ string, optname))
+ elif string.lower() in ('1', 'yes', 'true', 'on'):
+ return True
+ elif string.lower() in ('0', 'no', 'false', 'off'):
+ return False
+ else:
+ raise OptionError('Invalid value %r for option %s; use '
+ '1/0, yes/no, true/false, on/off' % (
+ string, optname))
+
+
+def get_int_opt(options, optname, default=None):
+ string = options.get(optname, default)
+ try:
+ return int(string)
+ except TypeError:
+ raise OptionError('Invalid type %r for option %s; you '
+ 'must give an integer value' % (
+ string, optname))
+ except ValueError:
+ raise OptionError('Invalid value %r for option %s; you '
+ 'must give an integer value' % (
+ string, optname))
+
+
+def get_list_opt(options, optname, default=None):
+ val = options.get(optname, default)
+ if isinstance(val, str):
+ return val.split()
+ elif isinstance(val, (list, tuple)):
+ return list(val)
+ else:
+ raise OptionError('Invalid type %r for option %s; you '
+ 'must give a list value' % (
+ val, optname))
+
+
+def docstring_headline(obj):
+ if not obj.__doc__:
+ return ''
+ res = []
+ for line in obj.__doc__.strip().splitlines():
+ if line.strip():
+ res.append(" " + line.strip())
+ else:
+ break
+ return ''.join(res).lstrip()
+
+
+def make_analysator(f):
+ """Return a static text analyser function that returns float values."""
+ def text_analyse(text):
+ try:
+ rv = f(text)
+ except Exception:
+ return 0.0
+ if not rv:
+ return 0.0
+ try:
+ return min(1.0, max(0.0, float(rv)))
+ except (ValueError, TypeError):
+ return 0.0
+ text_analyse.__doc__ = f.__doc__
+ return staticmethod(text_analyse)
+
+
+def shebang_matches(text, regex):
+ r"""Check if the given regular expression matches the last part of the
+ shebang if one exists.
+
+ >>> from pygments.util import shebang_matches
+ >>> shebang_matches('#!/usr/bin/env python', r'python(2\.\d)?')
+ True
+ >>> shebang_matches('#!/usr/bin/python2.4', r'python(2\.\d)?')
+ True
+ >>> shebang_matches('#!/usr/bin/python-ruby', r'python(2\.\d)?')
+ False
+ >>> shebang_matches('#!/usr/bin/python/ruby', r'python(2\.\d)?')
+ False
+ >>> shebang_matches('#!/usr/bin/startsomethingwith python',
+ ... r'python(2\.\d)?')
+ True
+
+ It also checks for common windows executable file extensions::
+
+ >>> shebang_matches('#!C:\\Python2.4\\Python.exe', r'python(2\.\d)?')
+ True
+
+ Parameters (``'-f'`` or ``'--foo'`` are ignored so ``'perl'`` does
+ the same as ``'perl -e'``)
+
+ Note that this method automatically searches the whole string (eg:
+ the regular expression is wrapped in ``'^$'``)
+ """
+ index = text.find('\n')
+ if index >= 0:
+ first_line = text[:index].lower()
+ else:
+ first_line = text.lower()
+ if first_line.startswith('#!'):
+ try:
+ found = [x for x in split_path_re.split(first_line[2:].strip())
+ if x and not x.startswith('-')][-1]
+ except IndexError:
+ return False
+ regex = re.compile(r'^%s(\.(exe|cmd|bat|bin))?$' % regex, re.IGNORECASE)
+ if regex.search(found) is not None:
+ return True
+ return False
+
+
+def doctype_matches(text, regex):
+ """Check if the doctype matches a regular expression (if present).
+
+ Note that this method only checks the first part of a DOCTYPE.
+ eg: 'html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"'
+ """
+ m = doctype_lookup_re.search(text)
+ if m is None:
+ return False
+ doctype = m.group(1)
+ return re.compile(regex, re.I).match(doctype.strip()) is not None
+
+
+def html_doctype_matches(text):
+ """Check if the file looks like it has a html doctype."""
+ return doctype_matches(text, r'html')
+
+
+_looks_like_xml_cache = {}
+
+
+def looks_like_xml(text):
+ """Check if a doctype exists or if we have some tags."""
+ if xml_decl_re.match(text):
+ return True
+ key = hash(text)
+ try:
+ return _looks_like_xml_cache[key]
+ except KeyError:
+ m = doctype_lookup_re.search(text)
+ if m is not None:
+ return True
+ rv = tag_re.search(text[:1000]) is not None
+ _looks_like_xml_cache[key] = rv
+ return rv
+
+
+def surrogatepair(c):
+ """Given a unicode character code with length greater than 16 bits,
+ return the two 16 bit surrogate pair.
+ """
+ # From example D28 of:
+ # http://www.unicode.org/book/ch03.pdf
+ return (0xd7c0 + (c >> 10), (0xdc00 + (c & 0x3ff)))
+
+
+def format_lines(var_name, seq, raw=False, indent_level=0):
+ """Formats a sequence of strings for output."""
+ lines = []
+ base_indent = ' ' * indent_level * 4
+ inner_indent = ' ' * (indent_level + 1) * 4
+ lines.append(base_indent + var_name + ' = (')
+ if raw:
+ # These should be preformatted reprs of, say, tuples.
+ for i in seq:
+ lines.append(inner_indent + i + ',')
+ else:
+ for i in seq:
+ # Force use of single quotes
+ r = repr(i + '"')
+ lines.append(inner_indent + r[:-2] + r[-1] + ',')
+ lines.append(base_indent + ')')
+ return '\n'.join(lines)
+
+
+def duplicates_removed(it, already_seen=()):
+ """
+ Returns a list with duplicates removed from the iterable `it`.
+
+ Order is preserved.
+ """
+ lst = []
+ seen = set()
+ for i in it:
+ if i in seen or i in already_seen:
+ continue
+ lst.append(i)
+ seen.add(i)
+ return lst
+
+
+class Future:
+ """Generic class to defer some work.
+
+ Handled specially in RegexLexerMeta, to support regex string construction at
+ first use.
+ """
+ def get(self):
+ raise NotImplementedError
+
+
+def guess_decode(text):
+ """Decode *text* with guessed encoding.
+
+ First try UTF-8; this should fail for non-UTF-8 encodings.
+ Then try the preferred locale encoding.
+ Fall back to latin-1, which always works.
+ """
+ try:
+ text = text.decode('utf-8')
+ return text, 'utf-8'
+ except UnicodeDecodeError:
+ try:
+ import locale
+ prefencoding = locale.getpreferredencoding()
+ text = text.decode()
+ return text, prefencoding
+ except (UnicodeDecodeError, LookupError):
+ text = text.decode('latin1')
+ return text, 'latin1'
+
+
+def guess_decode_from_terminal(text, term):
+ """Decode *text* coming from terminal *term*.
+
+ First try the terminal encoding, if given.
+ Then try UTF-8. Then try the preferred locale encoding.
+ Fall back to latin-1, which always works.
+ """
+ if getattr(term, 'encoding', None):
+ try:
+ text = text.decode(term.encoding)
+ except UnicodeDecodeError:
+ pass
+ else:
+ return text, term.encoding
+ return guess_decode(text)
+
+
+def terminal_encoding(term):
+ """Return our best guess of encoding for the given *term*."""
+ if getattr(term, 'encoding', None):
+ return term.encoding
+ import locale
+ return locale.getpreferredencoding()
+
+
+class UnclosingTextIOWrapper(TextIOWrapper):
+ # Don't close underlying buffer on destruction.
+ def close(self):
+ self.flush()
diff --git a/src/pip/_vendor/pyparsing.py b/src/pip/_vendor/pyparsing.py
deleted file mode 100644
index 7ebc7eb99..000000000
--- a/src/pip/_vendor/pyparsing.py
+++ /dev/null
@@ -1,7107 +0,0 @@
-# -*- coding: utf-8 -*-
-# module pyparsing.py
-#
-# Copyright (c) 2003-2019 Paul T. McGuire
-#
-# 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.
-#
-
-__doc__ = \
-"""
-pyparsing module - Classes and methods to define and execute parsing grammars
-=============================================================================
-
-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. With pyparsing, you don't need to learn
-a new syntax for defining grammars or matching expressions - the parsing
-module provides a library of classes that you use to construct the
-grammar directly in Python.
-
-Here is a program to parse "Hello, World!" (or any greeting of the form
-``"<salutation>, <addressee>!"``), built up using :class:`Word`,
-:class:`Literal`, and :class:`And` elements
-(the :class:`'+'<ParserElement.__add__>` operators create :class:`And` expressions,
-and the strings are auto-converted to :class:`Literal` expressions)::
-
- from pip._vendor.pyparsing import Word, alphas
-
- # define grammar of a greeting
- 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 '^' operators.
-
-The :class:`ParseResults` object returned from
-:class:`ParserElement.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
-
-
-Getting Started -
------------------
-Visit the classes :class:`ParserElement` and :class:`ParseResults` to
-see the base classes that most other pyparsing
-classes inherit from. Use the docstrings for examples of how to:
-
- - construct literal match expressions from :class:`Literal` and
- :class:`CaselessLiteral` classes
- - construct character word-group expressions using the :class:`Word`
- class
- - see how to create repetitive expressions using :class:`ZeroOrMore`
- and :class:`OneOrMore` classes
- - use :class:`'+'<And>`, :class:`'|'<MatchFirst>`, :class:`'^'<Or>`,
- and :class:`'&'<Each>` operators to combine simple expressions into
- more complex ones
- - associate names with your parsed results using
- :class:`ParserElement.setResultsName`
- - access the parsed data, which is returned as a :class:`ParseResults`
- object
- - find some helpful expression short-cuts like :class:`delimitedList`
- and :class:`oneOf`
- - find more useful common expressions in the :class:`pyparsing_common`
- namespace class
-"""
-
-__version__ = "2.4.7"
-__versionTime__ = "30 Mar 2020 00:43 UTC"
-__author__ = "Paul McGuire <ptmcg@users.sourceforge.net>"
-
-import string
-from weakref import ref as wkref
-import copy
-import sys
-import warnings
-import re
-import sre_constants
-import collections
-import pprint
-import traceback
-import types
-from datetime import datetime
-from operator import itemgetter
-import itertools
-from functools import wraps
-from contextlib import contextmanager
-
-try:
- # Python 3
- from itertools import filterfalse
-except ImportError:
- from itertools import ifilterfalse as filterfalse
-
-try:
- from _thread import RLock
-except ImportError:
- from threading import RLock
-
-try:
- # Python 3
- from collections.abc import Iterable
- from collections.abc import MutableMapping, Mapping
-except ImportError:
- # Python 2.7
- from collections import Iterable
- from collections import MutableMapping, Mapping
-
-try:
- from collections import OrderedDict as _OrderedDict
-except ImportError:
- try:
- from ordereddict import OrderedDict as _OrderedDict
- except ImportError:
- _OrderedDict = None
-
-try:
- from types import SimpleNamespace
-except ImportError:
- class SimpleNamespace: pass
-
-# version compatibility configuration
-__compat__ = SimpleNamespace()
-__compat__.__doc__ = """
- A cross-version compatibility configuration for pyparsing features that will be
- released in a future version. By setting values in this configuration to True,
- those features can be enabled in prior versions for compatibility development
- and testing.
-
- - collect_all_And_tokens - flag to enable fix for Issue #63 that fixes erroneous grouping
- of results names when an And expression is nested within an Or or MatchFirst; set to
- True to enable bugfix released in pyparsing 2.3.0, or False to preserve
- pre-2.3.0 handling of named results
-"""
-__compat__.collect_all_And_tokens = True
-
-__diag__ = SimpleNamespace()
-__diag__.__doc__ = """
-Diagnostic configuration (all default to False)
- - warn_multiple_tokens_in_named_alternation - flag to enable warnings when a results
- name is defined on a MatchFirst or Or expression with one or more And subexpressions
- (only warns if __compat__.collect_all_And_tokens is False)
- - warn_ungrouped_named_tokens_in_collection - flag to enable warnings when a results
- name is defined on a containing expression with ungrouped subexpressions that also
- have results names
- - warn_name_set_on_empty_Forward - flag to enable warnings whan a Forward is defined
- with a results name, but has no contents defined
- - warn_on_multiple_string_args_to_oneof - flag to enable warnings whan oneOf is
- incorrectly called with multiple str arguments
- - enable_debug_on_named_expressions - flag to auto-enable debug on all subsequent
- calls to ParserElement.setName()
-"""
-__diag__.warn_multiple_tokens_in_named_alternation = False
-__diag__.warn_ungrouped_named_tokens_in_collection = False
-__diag__.warn_name_set_on_empty_Forward = False
-__diag__.warn_on_multiple_string_args_to_oneof = False
-__diag__.enable_debug_on_named_expressions = False
-__diag__._all_names = [nm for nm in vars(__diag__) if nm.startswith("enable_") or nm.startswith("warn_")]
-
-def _enable_all_warnings():
- __diag__.warn_multiple_tokens_in_named_alternation = True
- __diag__.warn_ungrouped_named_tokens_in_collection = True
- __diag__.warn_name_set_on_empty_Forward = True
- __diag__.warn_on_multiple_string_args_to_oneof = True
-__diag__.enable_all_warnings = _enable_all_warnings
-
-
-__all__ = ['__version__', '__versionTime__', '__author__', '__compat__', '__diag__',
- 'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty',
- 'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal',
- 'PrecededBy', 'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or',
- 'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException',
- 'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException',
- 'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter',
- 'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', 'Char',
- 'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col',
- 'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString',
- 'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums',
- 'htmlComment', 'javaStyleComment', 'line', 'lineEnd', 'lineStart', 'lineno',
- 'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral',
- 'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables',
- 'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity',
- 'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd',
- 'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute',
- 'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation', 'locatedExpr', 'withClass',
- 'CloseMatch', 'tokenMap', 'pyparsing_common', 'pyparsing_unicode', 'unicode_set',
- 'conditionAsParseAction', 're',
- ]
-
-system_version = tuple(sys.version_info)[:3]
-PY_3 = system_version[0] == 3
-if PY_3:
- _MAX_INT = sys.maxsize
- basestring = str
- unichr = chr
- unicode = str
- _ustr = str
-
- # build list of single arg builtins, that can be used as parse actions
- singleArgBuiltins = [sum, len, sorted, reversed, list, tuple, set, any, all, min, max]
-
-else:
- _MAX_INT = sys.maxint
- range = xrange
-
- def _ustr(obj):
- """Drop-in replacement for str(obj) that tries to be Unicode
- friendly. It first tries str(obj). If that fails with
- a UnicodeEncodeError, then it tries unicode(obj). It then
- < returns the unicode object | encodes it with the default
- encoding | ... >.
- """
- if isinstance(obj, unicode):
- return obj
-
- try:
- # If this works, then _ustr(obj) has the same behaviour as str(obj), so
- # it won't break any existing code.
- return str(obj)
-
- except UnicodeEncodeError:
- # Else encode it
- ret = unicode(obj).encode(sys.getdefaultencoding(), 'xmlcharrefreplace')
- xmlcharref = Regex(r'&#\d+;')
- xmlcharref.setParseAction(lambda t: '\\u' + hex(int(t[0][2:-1]))[2:])
- return xmlcharref.transformString(ret)
-
- # build list of single arg builtins, tolerant of Python version, that can be used as parse actions
- singleArgBuiltins = []
- import __builtin__
-
- for fname in "sum len sorted reversed list tuple set any all min max".split():
- try:
- singleArgBuiltins.append(getattr(__builtin__, fname))
- except AttributeError:
- continue
-
-_generatorType = type((y for y in range(1)))
-
-def _xml_escape(data):
- """Escape &, <, >, ", ', etc. in a string of data."""
-
- # ampersand must be replaced first
- from_symbols = '&><"\''
- to_symbols = ('&' + s + ';' for s in "amp gt lt quot apos".split())
- for from_, to_ in zip(from_symbols, to_symbols):
- data = data.replace(from_, to_)
- return data
-
-alphas = string.ascii_uppercase + string.ascii_lowercase
-nums = "0123456789"
-hexnums = nums + "ABCDEFabcdef"
-alphanums = alphas + nums
-_bslash = chr(92)
-printables = "".join(c for c in string.printable if c not in string.whitespace)
-
-
-def conditionAsParseAction(fn, message=None, fatal=False):
- msg = message if message is not None else "failed user-defined condition"
- exc_type = ParseFatalException if fatal else ParseException
- fn = _trim_arity(fn)
-
- @wraps(fn)
- def pa(s, l, t):
- if not bool(fn(s, l, t)):
- raise exc_type(s, l, msg)
-
- return pa
-
-class ParseBaseException(Exception):
- """base exception class for all parsing runtime exceptions"""
- # Performance tuning: we construct a *lot* of these, so keep this
- # constructor as small and fast as possible
- def __init__(self, pstr, loc=0, msg=None, elem=None):
- self.loc = loc
- if msg is None:
- self.msg = pstr
- self.pstr = ""
- else:
- self.msg = msg
- self.pstr = pstr
- self.parserElement = elem
- self.args = (pstr, loc, msg)
-
- @classmethod
- def _from_exception(cls, pe):
- """
- internal factory method to simplify creating one type of ParseException
- from another - avoids having __init__ signature conflicts among subclasses
- """
- return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement)
-
- def __getattr__(self, aname):
- """supported attributes by name are:
- - lineno - returns the line number of the exception text
- - col - returns the column number of the exception text
- - line - returns the line containing the exception text
- """
- if aname == "lineno":
- return lineno(self.loc, self.pstr)
- elif aname in ("col", "column"):
- return col(self.loc, self.pstr)
- elif aname == "line":
- return line(self.loc, self.pstr)
- else:
- raise AttributeError(aname)
-
- def __str__(self):
- if self.pstr:
- if self.loc >= len(self.pstr):
- foundstr = ', found end of text'
- else:
- foundstr = (', found %r' % self.pstr[self.loc:self.loc + 1]).replace(r'\\', '\\')
- else:
- foundstr = ''
- return ("%s%s (at char %d), (line:%d, col:%d)" %
- (self.msg, foundstr, self.loc, self.lineno, self.column))
- def __repr__(self):
- return _ustr(self)
- def markInputline(self, markerString=">!<"):
- """Extracts the exception line from the input string, and marks
- the location of the exception with a special symbol.
- """
- line_str = self.line
- line_column = self.column - 1
- if markerString:
- line_str = "".join((line_str[:line_column],
- markerString, line_str[line_column:]))
- return line_str.strip()
- def __dir__(self):
- return "lineno col line".split() + dir(type(self))
-
-class ParseException(ParseBaseException):
- """
- Exception thrown when parse expressions don't match class;
- supported attributes by name are:
- - lineno - returns the line number of the exception text
- - col - returns the column number of the exception text
- - line - returns the line containing the exception text
-
- Example::
-
- try:
- Word(nums).setName("integer").parseString("ABC")
- except ParseException as pe:
- print(pe)
- print("column: {}".format(pe.col))
-
- prints::
-
- Expected integer (at char 0), (line:1, col:1)
- column: 1
-
- """
-
- @staticmethod
- def explain(exc, depth=16):
- """
- Method to take an exception and translate the Python internal traceback into a list
- of the pyparsing expressions that caused the exception to be raised.
-
- Parameters:
-
- - exc - exception raised during parsing (need not be a ParseException, in support
- of Python exceptions that might be raised in a parse action)
- - depth (default=16) - number of levels back in the stack trace to list expression
- and function names; if None, the full stack trace names will be listed; if 0, only
- the failing input line, marker, and exception string will be shown
-
- Returns a multi-line string listing the ParserElements and/or function names in the
- exception's stack trace.
-
- Note: the diagnostic output will include string representations of the expressions
- that failed to parse. These representations will be more helpful if you use `setName` to
- give identifiable names to your expressions. Otherwise they will use the default string
- forms, which may be cryptic to read.
-
- explain() is only supported under Python 3.
- """
- import inspect
-
- if depth is None:
- depth = sys.getrecursionlimit()
- ret = []
- if isinstance(exc, ParseBaseException):
- ret.append(exc.line)
- ret.append(' ' * (exc.col - 1) + '^')
- ret.append("{0}: {1}".format(type(exc).__name__, exc))
-
- if depth > 0:
- callers = inspect.getinnerframes(exc.__traceback__, context=depth)
- seen = set()
- for i, ff in enumerate(callers[-depth:]):
- frm = ff[0]
-
- f_self = frm.f_locals.get('self', None)
- if isinstance(f_self, ParserElement):
- if frm.f_code.co_name not in ('parseImpl', '_parseNoCache'):
- continue
- if f_self in seen:
- continue
- seen.add(f_self)
-
- self_type = type(f_self)
- ret.append("{0}.{1} - {2}".format(self_type.__module__,
- self_type.__name__,
- f_self))
- elif f_self is not None:
- self_type = type(f_self)
- ret.append("{0}.{1}".format(self_type.__module__,
- self_type.__name__))
- else:
- code = frm.f_code
- if code.co_name in ('wrapper', '<module>'):
- continue
-
- ret.append("{0}".format(code.co_name))
-
- depth -= 1
- if not depth:
- break
-
- return '\n'.join(ret)
-
-
-class ParseFatalException(ParseBaseException):
- """user-throwable exception thrown when inconsistent parse content
- is found; stops all parsing immediately"""
- pass
-
-class ParseSyntaxException(ParseFatalException):
- """just like :class:`ParseFatalException`, but thrown internally
- when an :class:`ErrorStop<And._ErrorStop>` ('-' operator) indicates
- that parsing is to stop immediately because an unbacktrackable
- syntax error has been found.
- """
- pass
-
-#~ class ReparseException(ParseBaseException):
- #~ """Experimental class - parse actions can raise this exception to cause
- #~ pyparsing to reparse the input string:
- #~ - with a modified input string, and/or
- #~ - with a modified start location
- #~ Set the values of the ReparseException in the constructor, and raise the
- #~ exception in a parse action to cause pyparsing to use the new string/location.
- #~ Setting the values as None causes no change to be made.
- #~ """
- #~ def __init_( self, newstring, restartLoc ):
- #~ self.newParseText = newstring
- #~ self.reparseLoc = restartLoc
-
-class RecursiveGrammarException(Exception):
- """exception thrown by :class:`ParserElement.validate` if the
- grammar could be improperly recursive
- """
- def __init__(self, parseElementList):
- self.parseElementTrace = parseElementList
-
- def __str__(self):
- return "RecursiveGrammarException: %s" % self.parseElementTrace
-
-class _ParseResultsWithOffset(object):
- def __init__(self, p1, p2):
- self.tup = (p1, p2)
- def __getitem__(self, i):
- return self.tup[i]
- def __repr__(self):
- return repr(self.tup[0])
- def setOffset(self, i):
- self.tup = (self.tup[0], i)
-
-class ParseResults(object):
- """Structured parse results, to provide multiple means of access to
- the parsed data:
-
- - as a list (``len(results)``)
- - by list index (``results[0], results[1]``, etc.)
- - by attribute (``results.<resultsName>`` - see :class:`ParserElement.setResultsName`)
-
- Example::
-
- integer = Word(nums)
- date_str = (integer.setResultsName("year") + '/'
- + integer.setResultsName("month") + '/'
- + integer.setResultsName("day"))
- # equivalent form:
- # date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
-
- # parseString returns a ParseResults object
- result = date_str.parseString("1999/12/31")
-
- def test(s, fn=repr):
- print("%s -> %s" % (s, fn(eval(s))))
- test("list(result)")
- test("result[0]")
- test("result['month']")
- test("result.day")
- test("'month' in result")
- test("'minutes' in result")
- test("result.dump()", str)
-
- prints::
-
- list(result) -> ['1999', '/', '12', '/', '31']
- result[0] -> '1999'
- result['month'] -> '12'
- result.day -> '31'
- 'month' in result -> True
- 'minutes' in result -> False
- result.dump() -> ['1999', '/', '12', '/', '31']
- - day: 31
- - month: 12
- - year: 1999
- """
- def __new__(cls, toklist=None, name=None, asList=True, modal=True):
- if isinstance(toklist, cls):
- return toklist
- retobj = object.__new__(cls)
- retobj.__doinit = True
- return retobj
-
- # Performance tuning: we construct a *lot* of these, so keep this
- # constructor as small and fast as possible
- def __init__(self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance):
- if self.__doinit:
- self.__doinit = False
- self.__name = None
- self.__parent = None
- self.__accumNames = {}
- self.__asList = asList
- self.__modal = modal
- if toklist is None:
- toklist = []
- if isinstance(toklist, list):
- self.__toklist = toklist[:]
- elif isinstance(toklist, _generatorType):
- self.__toklist = list(toklist)
- else:
- self.__toklist = [toklist]
- self.__tokdict = dict()
-
- if name is not None and name:
- if not modal:
- self.__accumNames[name] = 0
- if isinstance(name, int):
- name = _ustr(name) # will always return a str, but use _ustr for consistency
- self.__name = name
- if not (isinstance(toklist, (type(None), basestring, list)) and toklist in (None, '', [])):
- if isinstance(toklist, basestring):
- toklist = [toklist]
- if asList:
- if isinstance(toklist, ParseResults):
- self[name] = _ParseResultsWithOffset(ParseResults(toklist.__toklist), 0)
- else:
- self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]), 0)
- self[name].__name = name
- else:
- try:
- self[name] = toklist[0]
- except (KeyError, TypeError, IndexError):
- self[name] = toklist
-
- def __getitem__(self, i):
- if isinstance(i, (int, slice)):
- return self.__toklist[i]
- else:
- if i not in self.__accumNames:
- return self.__tokdict[i][-1][0]
- else:
- return ParseResults([v[0] for v in self.__tokdict[i]])
-
- def __setitem__(self, k, v, isinstance=isinstance):
- if isinstance(v, _ParseResultsWithOffset):
- self.__tokdict[k] = self.__tokdict.get(k, list()) + [v]
- sub = v[0]
- elif isinstance(k, (int, slice)):
- self.__toklist[k] = v
- sub = v
- else:
- self.__tokdict[k] = self.__tokdict.get(k, list()) + [_ParseResultsWithOffset(v, 0)]
- sub = v
- if isinstance(sub, ParseResults):
- sub.__parent = wkref(self)
-
- def __delitem__(self, i):
- if isinstance(i, (int, slice)):
- mylen = len(self.__toklist)
- del self.__toklist[i]
-
- # convert int to slice
- if isinstance(i, int):
- if i < 0:
- i += mylen
- i = slice(i, i + 1)
- # get removed indices
- removed = list(range(*i.indices(mylen)))
- removed.reverse()
- # fixup indices in token dictionary
- for name, occurrences in self.__tokdict.items():
- for j in removed:
- for k, (value, position) in enumerate(occurrences):
- occurrences[k] = _ParseResultsWithOffset(value, position - (position > j))
- else:
- del self.__tokdict[i]
-
- def __contains__(self, k):
- return k in self.__tokdict
-
- def __len__(self):
- return len(self.__toklist)
-
- def __bool__(self):
- return (not not self.__toklist)
- __nonzero__ = __bool__
-
- def __iter__(self):
- return iter(self.__toklist)
-
- def __reversed__(self):
- return iter(self.__toklist[::-1])
-
- def _iterkeys(self):
- if hasattr(self.__tokdict, "iterkeys"):
- return self.__tokdict.iterkeys()
- else:
- return iter(self.__tokdict)
-
- def _itervalues(self):
- return (self[k] for k in self._iterkeys())
-
- def _iteritems(self):
- return ((k, self[k]) for k in self._iterkeys())
-
- if PY_3:
- keys = _iterkeys
- """Returns an iterator of all named result keys."""
-
- values = _itervalues
- """Returns an iterator of all named result values."""
-
- items = _iteritems
- """Returns an iterator of all named result key-value tuples."""
-
- else:
- iterkeys = _iterkeys
- """Returns an iterator of all named result keys (Python 2.x only)."""
-
- itervalues = _itervalues
- """Returns an iterator of all named result values (Python 2.x only)."""
-
- iteritems = _iteritems
- """Returns an iterator of all named result key-value tuples (Python 2.x only)."""
-
- def keys(self):
- """Returns all named result keys (as a list in Python 2.x, as an iterator in Python 3.x)."""
- return list(self.iterkeys())
-
- def values(self):
- """Returns all named result values (as a list in Python 2.x, as an iterator in Python 3.x)."""
- return list(self.itervalues())
-
- def items(self):
- """Returns all named result key-values (as a list of tuples in Python 2.x, as an iterator in Python 3.x)."""
- return list(self.iteritems())
-
- def haskeys(self):
- """Since keys() returns an iterator, this method is helpful in bypassing
- code that looks for the existence of any defined results names."""
- return bool(self.__tokdict)
-
- def pop(self, *args, **kwargs):
- """
- Removes and returns item at specified index (default= ``last``).
- Supports both ``list`` and ``dict`` semantics for ``pop()``. If
- passed no argument or an integer argument, it will use ``list``
- semantics and pop tokens from the list of parsed tokens. If passed
- a non-integer argument (most likely a string), it will use ``dict``
- semantics and pop the corresponding value from any defined results
- names. A second default return value argument is supported, just as in
- ``dict.pop()``.
-
- Example::
-
- def remove_first(tokens):
- tokens.pop(0)
- print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
- print(OneOrMore(Word(nums)).addParseAction(remove_first).parseString("0 123 321")) # -> ['123', '321']
-
- label = Word(alphas)
- patt = label("LABEL") + OneOrMore(Word(nums))
- print(patt.parseString("AAB 123 321").dump())
-
- # Use pop() in a parse action to remove named result (note that corresponding value is not
- # removed from list form of results)
- def remove_LABEL(tokens):
- tokens.pop("LABEL")
- return tokens
- patt.addParseAction(remove_LABEL)
- print(patt.parseString("AAB 123 321").dump())
-
- prints::
-
- ['AAB', '123', '321']
- - LABEL: AAB
-
- ['AAB', '123', '321']
- """
- if not args:
- args = [-1]
- for k, v in kwargs.items():
- if k == 'default':
- args = (args[0], v)
- else:
- raise TypeError("pop() got an unexpected keyword argument '%s'" % k)
- if (isinstance(args[0], int)
- or len(args) == 1
- or args[0] in self):
- index = args[0]
- ret = self[index]
- del self[index]
- return ret
- else:
- defaultvalue = args[1]
- return defaultvalue
-
- def get(self, key, defaultValue=None):
- """
- Returns named result matching the given key, or if there is no
- such name, then returns the given ``defaultValue`` or ``None`` if no
- ``defaultValue`` is specified.
-
- Similar to ``dict.get()``.
-
- Example::
-
- integer = Word(nums)
- date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
-
- result = date_str.parseString("1999/12/31")
- print(result.get("year")) # -> '1999'
- print(result.get("hour", "not specified")) # -> 'not specified'
- print(result.get("hour")) # -> None
- """
- if key in self:
- return self[key]
- else:
- return defaultValue
-
- def insert(self, index, insStr):
- """
- Inserts new element at location index in the list of parsed tokens.
-
- Similar to ``list.insert()``.
-
- Example::
-
- print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
-
- # use a parse action to insert the parse location in the front of the parsed results
- def insert_locn(locn, tokens):
- tokens.insert(0, locn)
- print(OneOrMore(Word(nums)).addParseAction(insert_locn).parseString("0 123 321")) # -> [0, '0', '123', '321']
- """
- self.__toklist.insert(index, insStr)
- # fixup indices in token dictionary
- for name, occurrences in self.__tokdict.items():
- for k, (value, position) in enumerate(occurrences):
- occurrences[k] = _ParseResultsWithOffset(value, position + (position > index))
-
- def append(self, item):
- """
- Add single element to end of ParseResults list of elements.
-
- Example::
-
- print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
-
- # use a parse action to compute the sum of the parsed integers, and add it to the end
- def append_sum(tokens):
- tokens.append(sum(map(int, tokens)))
- print(OneOrMore(Word(nums)).addParseAction(append_sum).parseString("0 123 321")) # -> ['0', '123', '321', 444]
- """
- self.__toklist.append(item)
-
- def extend(self, itemseq):
- """
- Add sequence of elements to end of ParseResults list of elements.
-
- Example::
-
- patt = OneOrMore(Word(alphas))
-
- # use a parse action to append the reverse of the matched strings, to make a palindrome
- def make_palindrome(tokens):
- tokens.extend(reversed([t[::-1] for t in tokens]))
- return ''.join(tokens)
- print(patt.addParseAction(make_palindrome).parseString("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl'
- """
- if isinstance(itemseq, ParseResults):
- self.__iadd__(itemseq)
- else:
- self.__toklist.extend(itemseq)
-
- def clear(self):
- """
- Clear all elements and results names.
- """
- del self.__toklist[:]
- self.__tokdict.clear()
-
- def __getattr__(self, name):
- try:
- return self[name]
- except KeyError:
- return ""
-
- def __add__(self, other):
- ret = self.copy()
- ret += other
- return ret
-
- def __iadd__(self, other):
- if other.__tokdict:
- offset = len(self.__toklist)
- addoffset = lambda a: offset if a < 0 else a + offset
- otheritems = other.__tokdict.items()
- otherdictitems = [(k, _ParseResultsWithOffset(v[0], addoffset(v[1])))
- for k, vlist in otheritems for v in vlist]
- for k, v in otherdictitems:
- self[k] = v
- if isinstance(v[0], ParseResults):
- v[0].__parent = wkref(self)
-
- self.__toklist += other.__toklist
- self.__accumNames.update(other.__accumNames)
- return self
-
- def __radd__(self, other):
- if isinstance(other, int) and other == 0:
- # useful for merging many ParseResults using sum() builtin
- return self.copy()
- else:
- # this may raise a TypeError - so be it
- return other + self
-
- def __repr__(self):
- return "(%s, %s)" % (repr(self.__toklist), repr(self.__tokdict))
-
- def __str__(self):
- return '[' + ', '.join(_ustr(i) if isinstance(i, ParseResults) else repr(i) for i in self.__toklist) + ']'
-
- def _asStringList(self, sep=''):
- out = []
- for item in self.__toklist:
- if out and sep:
- out.append(sep)
- if isinstance(item, ParseResults):
- out += item._asStringList()
- else:
- out.append(_ustr(item))
- return out
-
- def asList(self):
- """
- Returns the parse results as a nested list of matching tokens, all converted to strings.
-
- Example::
-
- patt = OneOrMore(Word(alphas))
- result = patt.parseString("sldkj lsdkj sldkj")
- # even though the result prints in string-like form, it is actually a pyparsing ParseResults
- print(type(result), result) # -> <class 'pyparsing.ParseResults'> ['sldkj', 'lsdkj', 'sldkj']
-
- # Use asList() to create an actual list
- result_list = result.asList()
- print(type(result_list), result_list) # -> <class 'list'> ['sldkj', 'lsdkj', 'sldkj']
- """
- return [res.asList() if isinstance(res, ParseResults) else res for res in self.__toklist]
-
- def asDict(self):
- """
- Returns the named parse results as a nested dictionary.
-
- Example::
-
- integer = Word(nums)
- date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
-
- result = date_str.parseString('12/31/1999')
- print(type(result), repr(result)) # -> <class 'pyparsing.ParseResults'> (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]})
-
- result_dict = result.asDict()
- print(type(result_dict), repr(result_dict)) # -> <class 'dict'> {'day': '1999', 'year': '12', 'month': '31'}
-
- # even though a ParseResults supports dict-like access, sometime you just need to have a dict
- import json
- print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable
- print(json.dumps(result.asDict())) # -> {"month": "31", "day": "1999", "year": "12"}
- """
- if PY_3:
- item_fn = self.items
- else:
- item_fn = self.iteritems
-
- def toItem(obj):
- if isinstance(obj, ParseResults):
- if obj.haskeys():
- return obj.asDict()
- else:
- return [toItem(v) for v in obj]
- else:
- return obj
-
- return dict((k, toItem(v)) for k, v in item_fn())
-
- def copy(self):
- """
- Returns a new copy of a :class:`ParseResults` object.
- """
- ret = ParseResults(self.__toklist)
- ret.__tokdict = dict(self.__tokdict.items())
- ret.__parent = self.__parent
- ret.__accumNames.update(self.__accumNames)
- ret.__name = self.__name
- return ret
-
- def asXML(self, doctag=None, namedItemsOnly=False, indent="", formatted=True):
- """
- (Deprecated) Returns the parse results as XML. Tags are created for tokens and lists that have defined results names.
- """
- nl = "\n"
- out = []
- namedItems = dict((v[1], k) for (k, vlist) in self.__tokdict.items()
- for v in vlist)
- nextLevelIndent = indent + " "
-
- # collapse out indents if formatting is not desired
- if not formatted:
- indent = ""
- nextLevelIndent = ""
- nl = ""
-
- selfTag = None
- if doctag is not None:
- selfTag = doctag
- else:
- if self.__name:
- selfTag = self.__name
-
- if not selfTag:
- if namedItemsOnly:
- return ""
- else:
- selfTag = "ITEM"
-
- out += [nl, indent, "<", selfTag, ">"]
-
- for i, res in enumerate(self.__toklist):
- if isinstance(res, ParseResults):
- if i in namedItems:
- out += [res.asXML(namedItems[i],
- namedItemsOnly and doctag is None,
- nextLevelIndent,
- formatted)]
- else:
- out += [res.asXML(None,
- namedItemsOnly and doctag is None,
- nextLevelIndent,
- formatted)]
- else:
- # individual token, see if there is a name for it
- resTag = None
- if i in namedItems:
- resTag = namedItems[i]
- if not resTag:
- if namedItemsOnly:
- continue
- else:
- resTag = "ITEM"
- xmlBodyText = _xml_escape(_ustr(res))
- out += [nl, nextLevelIndent, "<", resTag, ">",
- xmlBodyText,
- "</", resTag, ">"]
-
- out += [nl, indent, "</", selfTag, ">"]
- return "".join(out)
-
- def __lookup(self, sub):
- for k, vlist in self.__tokdict.items():
- for v, loc in vlist:
- if sub is v:
- return k
- return None
-
- def getName(self):
- r"""
- Returns the results name for this token expression. Useful when several
- different expressions might match at a particular location.
-
- Example::
-
- integer = Word(nums)
- ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d")
- house_number_expr = Suppress('#') + Word(nums, alphanums)
- user_data = (Group(house_number_expr)("house_number")
- | Group(ssn_expr)("ssn")
- | Group(integer)("age"))
- user_info = OneOrMore(user_data)
-
- result = user_info.parseString("22 111-22-3333 #221B")
- for item in result:
- print(item.getName(), ':', item[0])
-
- prints::
-
- age : 22
- ssn : 111-22-3333
- house_number : 221B
- """
- if self.__name:
- return self.__name
- elif self.__parent:
- par = self.__parent()
- if par:
- return par.__lookup(self)
- else:
- return None
- elif (len(self) == 1
- and len(self.__tokdict) == 1
- and next(iter(self.__tokdict.values()))[0][1] in (0, -1)):
- return next(iter(self.__tokdict.keys()))
- else:
- return None
-
- def dump(self, indent='', full=True, include_list=True, _depth=0):
- """
- Diagnostic method for listing out the contents of
- a :class:`ParseResults`. Accepts an optional ``indent`` argument so
- that this string can be embedded in a nested display of other data.
-
- Example::
-
- integer = Word(nums)
- date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
-
- result = date_str.parseString('12/31/1999')
- print(result.dump())
-
- prints::
-
- ['12', '/', '31', '/', '1999']
- - day: 1999
- - month: 31
- - year: 12
- """
- out = []
- NL = '\n'
- if include_list:
- out.append(indent + _ustr(self.asList()))
- else:
- out.append('')
-
- if full:
- if self.haskeys():
- items = sorted((str(k), v) for k, v in self.items())
- for k, v in items:
- if out:
- out.append(NL)
- out.append("%s%s- %s: " % (indent, (' ' * _depth), k))
- if isinstance(v, ParseResults):
- if v:
- out.append(v.dump(indent=indent, full=full, include_list=include_list, _depth=_depth + 1))
- else:
- out.append(_ustr(v))
- else:
- out.append(repr(v))
- elif any(isinstance(vv, ParseResults) for vv in self):
- v = self
- for i, vv in enumerate(v):
- if isinstance(vv, ParseResults):
- out.append("\n%s%s[%d]:\n%s%s%s" % (indent,
- (' ' * (_depth)),
- i,
- indent,
- (' ' * (_depth + 1)),
- vv.dump(indent=indent,
- full=full,
- include_list=include_list,
- _depth=_depth + 1)))
- else:
- out.append("\n%s%s[%d]:\n%s%s%s" % (indent,
- (' ' * (_depth)),
- i,
- indent,
- (' ' * (_depth + 1)),
- _ustr(vv)))
-
- return "".join(out)
-
- def pprint(self, *args, **kwargs):
- """
- Pretty-printer for parsed results as a list, using the
- `pprint <https://docs.python.org/3/library/pprint.html>`_ module.
- Accepts additional positional or keyword args as defined for
- `pprint.pprint <https://docs.python.org/3/library/pprint.html#pprint.pprint>`_ .
-
- Example::
-
- ident = Word(alphas, alphanums)
- num = Word(nums)
- func = Forward()
- term = ident | num | Group('(' + func + ')')
- func <<= ident + Group(Optional(delimitedList(term)))
- result = func.parseString("fna a,b,(fnb c,d,200),100")
- result.pprint(width=40)
-
- prints::
-
- ['fna',
- ['a',
- 'b',
- ['(', 'fnb', ['c', 'd', '200'], ')'],
- '100']]
- """
- pprint.pprint(self.asList(), *args, **kwargs)
-
- # add support for pickle protocol
- def __getstate__(self):
- return (self.__toklist,
- (self.__tokdict.copy(),
- self.__parent is not None and self.__parent() or None,
- self.__accumNames,
- self.__name))
-
- def __setstate__(self, state):
- self.__toklist = state[0]
- self.__tokdict, par, inAccumNames, self.__name = state[1]
- self.__accumNames = {}
- self.__accumNames.update(inAccumNames)
- if par is not None:
- self.__parent = wkref(par)
- else:
- self.__parent = None
-
- def __getnewargs__(self):
- return self.__toklist, self.__name, self.__asList, self.__modal
-
- def __dir__(self):
- return dir(type(self)) + list(self.keys())
-
- @classmethod
- def from_dict(cls, other, name=None):
- """
- Helper classmethod to construct a ParseResults from a dict, preserving the
- name-value relations as results names. If an optional 'name' argument is
- given, a nested ParseResults will be returned
- """
- def is_iterable(obj):
- try:
- iter(obj)
- except Exception:
- return False
- else:
- if PY_3:
- return not isinstance(obj, (str, bytes))
- else:
- return not isinstance(obj, basestring)
-
- ret = cls([])
- for k, v in other.items():
- if isinstance(v, Mapping):
- ret += cls.from_dict(v, name=k)
- else:
- ret += cls([v], name=k, asList=is_iterable(v))
- if name is not None:
- ret = cls([ret], name=name)
- return ret
-
-MutableMapping.register(ParseResults)
-
-def col (loc, strg):
- """Returns current column within a string, counting newlines as line separators.
- The first column is number 1.
-
- Note: the default parsing behavior is to expand tabs in the input string
- before starting the parsing process. See
- :class:`ParserElement.parseString` for more
- information on parsing strings containing ``<TAB>`` s, and suggested
- methods to maintain a consistent view of the parsed string, the parse
- location, and line and column positions within the parsed string.
- """
- s = strg
- return 1 if 0 < loc < len(s) and s[loc-1] == '\n' else loc - s.rfind("\n", 0, loc)
-
-def lineno(loc, strg):
- """Returns current line number within a string, counting newlines as line separators.
- The first line is number 1.
-
- Note - the default parsing behavior is to expand tabs in the input string
- before starting the parsing process. See :class:`ParserElement.parseString`
- for more information on parsing strings containing ``<TAB>`` s, and
- suggested methods to maintain a consistent view of the parsed string, the
- parse location, and line and column positions within the parsed string.
- """
- return strg.count("\n", 0, loc) + 1
-
-def line(loc, strg):
- """Returns the line of text containing loc within a string, counting newlines as line separators.
- """
- lastCR = strg.rfind("\n", 0, loc)
- nextCR = strg.find("\n", loc)
- if nextCR >= 0:
- return strg[lastCR + 1:nextCR]
- else:
- return strg[lastCR + 1:]
-
-def _defaultStartDebugAction(instring, loc, expr):
- print(("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % (lineno(loc, instring), col(loc, instring))))
-
-def _defaultSuccessDebugAction(instring, startloc, endloc, expr, toks):
- print("Matched " + _ustr(expr) + " -> " + str(toks.asList()))
-
-def _defaultExceptionDebugAction(instring, loc, expr, exc):
- print("Exception raised:" + _ustr(exc))
-
-def nullDebugAction(*args):
- """'Do-nothing' debug action, to suppress debugging output during parsing."""
- pass
-
-# Only works on Python 3.x - nonlocal is toxic to Python 2 installs
-#~ 'decorator to trim function calls to match the arity of the target'
-#~ def _trim_arity(func, maxargs=3):
- #~ if func in singleArgBuiltins:
- #~ return lambda s,l,t: func(t)
- #~ limit = 0
- #~ foundArity = False
- #~ def wrapper(*args):
- #~ nonlocal limit,foundArity
- #~ while 1:
- #~ try:
- #~ ret = func(*args[limit:])
- #~ foundArity = True
- #~ return ret
- #~ except TypeError:
- #~ if limit == maxargs or foundArity:
- #~ raise
- #~ limit += 1
- #~ continue
- #~ return wrapper
-
-# this version is Python 2.x-3.x cross-compatible
-'decorator to trim function calls to match the arity of the target'
-def _trim_arity(func, maxargs=2):
- if func in singleArgBuiltins:
- return lambda s, l, t: func(t)
- limit = [0]
- foundArity = [False]
-
- # traceback return data structure changed in Py3.5 - normalize back to plain tuples
- if system_version[:2] >= (3, 5):
- def extract_stack(limit=0):
- # special handling for Python 3.5.0 - extra deep call stack by 1
- offset = -3 if system_version == (3, 5, 0) else -2
- frame_summary = traceback.extract_stack(limit=-offset + limit - 1)[offset]
- return [frame_summary[:2]]
- def extract_tb(tb, limit=0):
- frames = traceback.extract_tb(tb, limit=limit)
- frame_summary = frames[-1]
- return [frame_summary[:2]]
- else:
- extract_stack = traceback.extract_stack
- extract_tb = traceback.extract_tb
-
- # synthesize what would be returned by traceback.extract_stack at the call to
- # user's parse action 'func', so that we don't incur call penalty at parse time
-
- LINE_DIFF = 6
- # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND
- # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!!
- this_line = extract_stack(limit=2)[-1]
- pa_call_line_synth = (this_line[0], this_line[1] + LINE_DIFF)
-
- def wrapper(*args):
- while 1:
- try:
- ret = func(*args[limit[0]:])
- foundArity[0] = True
- return ret
- except TypeError:
- # re-raise TypeErrors if they did not come from our arity testing
- if foundArity[0]:
- raise
- else:
- try:
- tb = sys.exc_info()[-1]
- if not extract_tb(tb, limit=2)[-1][:2] == pa_call_line_synth:
- raise
- finally:
- try:
- del tb
- except NameError:
- pass
-
- if limit[0] <= maxargs:
- limit[0] += 1
- continue
- raise
-
- # copy func name to wrapper for sensible debug output
- func_name = "<parse action>"
- try:
- func_name = getattr(func, '__name__',
- getattr(func, '__class__').__name__)
- except Exception:
- func_name = str(func)
- wrapper.__name__ = func_name
-
- return wrapper
-
-
-class ParserElement(object):
- """Abstract base level parser element class."""
- DEFAULT_WHITE_CHARS = " \n\t\r"
- verbose_stacktrace = False
-
- @staticmethod
- def setDefaultWhitespaceChars(chars):
- r"""
- Overrides the default whitespace chars
-
- Example::
-
- # default whitespace chars are space, <TAB> and newline
- OneOrMore(Word(alphas)).parseString("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl']
-
- # change to just treat newline as significant
- ParserElement.setDefaultWhitespaceChars(" \t")
- OneOrMore(Word(alphas)).parseString("abc def\nghi jkl") # -> ['abc', 'def']
- """
- ParserElement.DEFAULT_WHITE_CHARS = chars
-
- @staticmethod
- def inlineLiteralsUsing(cls):
- """
- Set class to be used for inclusion of string literals into a parser.
-
- Example::
-
- # default literal class used is Literal
- integer = Word(nums)
- date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
-
- date_str.parseString("1999/12/31") # -> ['1999', '/', '12', '/', '31']
-
-
- # change to Suppress
- ParserElement.inlineLiteralsUsing(Suppress)
- date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
-
- date_str.parseString("1999/12/31") # -> ['1999', '12', '31']
- """
- ParserElement._literalStringClass = cls
-
- @classmethod
- def _trim_traceback(cls, tb):
- while tb.tb_next:
- tb = tb.tb_next
- return tb
-
- def __init__(self, savelist=False):
- self.parseAction = list()
- self.failAction = None
- # ~ self.name = "<unknown>" # don't define self.name, let subclasses try/except upcall
- self.strRepr = None
- self.resultsName = None
- self.saveAsList = savelist
- self.skipWhitespace = True
- self.whiteChars = set(ParserElement.DEFAULT_WHITE_CHARS)
- self.copyDefaultWhiteChars = True
- self.mayReturnEmpty = False # used when checking for left-recursion
- self.keepTabs = False
- self.ignoreExprs = list()
- self.debug = False
- self.streamlined = False
- self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index
- self.errmsg = ""
- self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all)
- self.debugActions = (None, None, None) # custom debug actions
- self.re = None
- self.callPreparse = True # used to avoid redundant calls to preParse
- self.callDuringTry = False
-
- def copy(self):
- """
- Make a copy of this :class:`ParserElement`. Useful for defining
- different parse actions for the same parsing pattern, using copies of
- the original parse element.
-
- Example::
-
- integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
- integerK = integer.copy().addParseAction(lambda toks: toks[0] * 1024) + Suppress("K")
- integerM = integer.copy().addParseAction(lambda toks: toks[0] * 1024 * 1024) + Suppress("M")
-
- print(OneOrMore(integerK | integerM | integer).parseString("5K 100 640K 256M"))
-
- prints::
-
- [5120, 100, 655360, 268435456]
-
- Equivalent form of ``expr.copy()`` is just ``expr()``::
-
- integerM = integer().addParseAction(lambda toks: toks[0] * 1024 * 1024) + Suppress("M")
- """
- cpy = copy.copy(self)
- cpy.parseAction = self.parseAction[:]
- cpy.ignoreExprs = self.ignoreExprs[:]
- if self.copyDefaultWhiteChars:
- cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS
- return cpy
-
- def setName(self, name):
- """
- Define name for this expression, makes debugging and exception messages clearer.
-
- Example::
-
- Word(nums).parseString("ABC") # -> Exception: Expected W:(0123...) (at char 0), (line:1, col:1)
- Word(nums).setName("integer").parseString("ABC") # -> Exception: Expected integer (at char 0), (line:1, col:1)
- """
- self.name = name
- self.errmsg = "Expected " + self.name
- if __diag__.enable_debug_on_named_expressions:
- self.setDebug()
- return self
-
- def setResultsName(self, name, listAllMatches=False):
- """
- Define name for referencing matching tokens as a nested attribute
- of the returned parse results.
- NOTE: this returns a *copy* of the original :class:`ParserElement` object;
- this is so that the client can define a basic element, such as an
- integer, and reference it in multiple places with different names.
-
- You can also set results names using the abbreviated syntax,
- ``expr("name")`` in place of ``expr.setResultsName("name")``
- - see :class:`__call__`.
-
- Example::
-
- date_str = (integer.setResultsName("year") + '/'
- + integer.setResultsName("month") + '/'
- + integer.setResultsName("day"))
-
- # equivalent form:
- date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
- """
- return self._setResultsName(name, listAllMatches)
-
- def _setResultsName(self, name, listAllMatches=False):
- newself = self.copy()
- if name.endswith("*"):
- name = name[:-1]
- listAllMatches = True
- newself.resultsName = name
- newself.modalResults = not listAllMatches
- return newself
-
- def setBreak(self, breakFlag=True):
- """Method to invoke the Python pdb debugger when this element is
- about to be parsed. Set ``breakFlag`` to True to enable, False to
- disable.
- """
- if breakFlag:
- _parseMethod = self._parse
- def breaker(instring, loc, doActions=True, callPreParse=True):
- import pdb
- # this call to pdb.set_trace() is intentional, not a checkin error
- pdb.set_trace()
- return _parseMethod(instring, loc, doActions, callPreParse)
- breaker._originalParseMethod = _parseMethod
- self._parse = breaker
- else:
- if hasattr(self._parse, "_originalParseMethod"):
- self._parse = self._parse._originalParseMethod
- return self
-
- def setParseAction(self, *fns, **kwargs):
- """
- Define one or more actions to perform when successfully matching parse element definition.
- Parse action fn is a callable method with 0-3 arguments, called as ``fn(s, loc, toks)`` ,
- ``fn(loc, toks)`` , ``fn(toks)`` , or just ``fn()`` , where:
-
- - s = the original string being parsed (see note below)
- - loc = the location of the matching substring
- - toks = a list of the matched tokens, packaged as a :class:`ParseResults` object
-
- If the functions in fns modify the tokens, they can return them as the return
- value from fn, and the modified list of tokens will replace the original.
- Otherwise, fn does not need to return any value.
-
- If None is passed as the parse action, all previously added parse actions for this
- expression are cleared.
-
- Optional keyword arguments:
- - callDuringTry = (default= ``False``) indicate if parse action should be run during lookaheads and alternate testing
-
- Note: the default parsing behavior is to expand tabs in the input string
- before starting the parsing process. See :class:`parseString for more
- information on parsing strings containing ``<TAB>`` s, and suggested
- methods to maintain a consistent view of the parsed string, the parse
- location, and line and column positions within the parsed string.
-
- Example::
-
- integer = Word(nums)
- date_str = integer + '/' + integer + '/' + integer
-
- date_str.parseString("1999/12/31") # -> ['1999', '/', '12', '/', '31']
-
- # use parse action to convert to ints at parse time
- integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
- date_str = integer + '/' + integer + '/' + integer
-
- # note that integer fields are now ints, not strings
- date_str.parseString("1999/12/31") # -> [1999, '/', 12, '/', 31]
- """
- if list(fns) == [None,]:
- self.parseAction = []
- else:
- if not all(callable(fn) for fn in fns):
- raise TypeError("parse actions must be callable")
- self.parseAction = list(map(_trim_arity, list(fns)))
- self.callDuringTry = kwargs.get("callDuringTry", False)
- return self
-
- def addParseAction(self, *fns, **kwargs):
- """
- Add one or more parse actions to expression's list of parse actions. See :class:`setParseAction`.
-
- See examples in :class:`copy`.
- """
- self.parseAction += list(map(_trim_arity, list(fns)))
- self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
- return self
-
- def addCondition(self, *fns, **kwargs):
- """Add a boolean predicate function to expression's list of parse actions. See
- :class:`setParseAction` for function call signatures. Unlike ``setParseAction``,
- functions passed to ``addCondition`` need to return boolean success/fail of the condition.
-
- Optional keyword arguments:
- - message = define a custom message to be used in the raised exception
- - fatal = if True, will raise ParseFatalException to stop parsing immediately; otherwise will raise ParseException
-
- Example::
-
- integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
- year_int = integer.copy()
- year_int.addCondition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later")
- date_str = year_int + '/' + integer + '/' + integer
-
- result = date_str.parseString("1999/12/31") # -> Exception: Only support years 2000 and later (at char 0), (line:1, col:1)
- """
- for fn in fns:
- self.parseAction.append(conditionAsParseAction(fn, message=kwargs.get('message'),
- fatal=kwargs.get('fatal', False)))
-
- self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
- return self
-
- def setFailAction(self, fn):
- """Define action to perform if parsing fails at this expression.
- Fail acton fn is a callable function that takes the arguments
- ``fn(s, loc, expr, err)`` where:
- - s = string being parsed
- - loc = location where expression match was attempted and failed
- - expr = the parse expression that failed
- - err = the exception thrown
- The function returns no value. It may throw :class:`ParseFatalException`
- if it is desired to stop parsing immediately."""
- self.failAction = fn
- return self
-
- def _skipIgnorables(self, instring, loc):
- exprsFound = True
- while exprsFound:
- exprsFound = False
- for e in self.ignoreExprs:
- try:
- while 1:
- loc, dummy = e._parse(instring, loc)
- exprsFound = True
- except ParseException:
- pass
- return loc
-
- def preParse(self, instring, loc):
- if self.ignoreExprs:
- loc = self._skipIgnorables(instring, loc)
-
- if self.skipWhitespace:
- wt = self.whiteChars
- instrlen = len(instring)
- while loc < instrlen and instring[loc] in wt:
- loc += 1
-
- return loc
-
- def parseImpl(self, instring, loc, doActions=True):
- return loc, []
-
- def postParse(self, instring, loc, tokenlist):
- return tokenlist
-
- # ~ @profile
- def _parseNoCache(self, instring, loc, doActions=True, callPreParse=True):
- TRY, MATCH, FAIL = 0, 1, 2
- debugging = (self.debug) # and doActions)
-
- if debugging or self.failAction:
- # ~ print ("Match", self, "at loc", loc, "(%d, %d)" % (lineno(loc, instring), col(loc, instring)))
- if self.debugActions[TRY]:
- self.debugActions[TRY](instring, loc, self)
- try:
- if callPreParse and self.callPreparse:
- preloc = self.preParse(instring, loc)
- else:
- preloc = loc
- tokensStart = preloc
- if self.mayIndexError or preloc >= len(instring):
- try:
- loc, tokens = self.parseImpl(instring, preloc, doActions)
- except IndexError:
- raise ParseException(instring, len(instring), self.errmsg, self)
- else:
- loc, tokens = self.parseImpl(instring, preloc, doActions)
- except Exception as err:
- # ~ print ("Exception raised:", err)
- if self.debugActions[FAIL]:
- self.debugActions[FAIL](instring, tokensStart, self, err)
- if self.failAction:
- self.failAction(instring, tokensStart, self, err)
- raise
- else:
- if callPreParse and self.callPreparse:
- preloc = self.preParse(instring, loc)
- else:
- preloc = loc
- tokensStart = preloc
- if self.mayIndexError or preloc >= len(instring):
- try:
- loc, tokens = self.parseImpl(instring, preloc, doActions)
- except IndexError:
- raise ParseException(instring, len(instring), self.errmsg, self)
- else:
- loc, tokens = self.parseImpl(instring, preloc, doActions)
-
- tokens = self.postParse(instring, loc, tokens)
-
- retTokens = ParseResults(tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults)
- if self.parseAction and (doActions or self.callDuringTry):
- if debugging:
- try:
- for fn in self.parseAction:
- try:
- tokens = fn(instring, tokensStart, retTokens)
- except IndexError as parse_action_exc:
- exc = ParseException("exception raised in parse action")
- exc.__cause__ = parse_action_exc
- raise exc
-
- if tokens is not None and tokens is not retTokens:
- retTokens = ParseResults(tokens,
- self.resultsName,
- asList=self.saveAsList and isinstance(tokens, (ParseResults, list)),
- modal=self.modalResults)
- except Exception as err:
- # ~ print "Exception raised in user parse action:", err
- if self.debugActions[FAIL]:
- self.debugActions[FAIL](instring, tokensStart, self, err)
- raise
- else:
- for fn in self.parseAction:
- try:
- tokens = fn(instring, tokensStart, retTokens)
- except IndexError as parse_action_exc:
- exc = ParseException("exception raised in parse action")
- exc.__cause__ = parse_action_exc
- raise exc
-
- if tokens is not None and tokens is not retTokens:
- retTokens = ParseResults(tokens,
- self.resultsName,
- asList=self.saveAsList and isinstance(tokens, (ParseResults, list)),
- modal=self.modalResults)
- if debugging:
- # ~ print ("Matched", self, "->", retTokens.asList())
- if self.debugActions[MATCH]:
- self.debugActions[MATCH](instring, tokensStart, loc, self, retTokens)
-
- return loc, retTokens
-
- def tryParse(self, instring, loc):
- try:
- return self._parse(instring, loc, doActions=False)[0]
- except ParseFatalException:
- raise ParseException(instring, loc, self.errmsg, self)
-
- def canParseNext(self, instring, loc):
- try:
- self.tryParse(instring, loc)
- except (ParseException, IndexError):
- return False
- else:
- return True
-
- class _UnboundedCache(object):
- def __init__(self):
- cache = {}
- self.not_in_cache = not_in_cache = object()
-
- def get(self, key):
- return cache.get(key, not_in_cache)
-
- def set(self, key, value):
- cache[key] = value
-
- def clear(self):
- cache.clear()
-
- def cache_len(self):
- return len(cache)
-
- self.get = types.MethodType(get, self)
- self.set = types.MethodType(set, self)
- self.clear = types.MethodType(clear, self)
- self.__len__ = types.MethodType(cache_len, self)
-
- if _OrderedDict is not None:
- class _FifoCache(object):
- def __init__(self, size):
- self.not_in_cache = not_in_cache = object()
-
- cache = _OrderedDict()
-
- def get(self, key):
- return cache.get(key, not_in_cache)
-
- def set(self, key, value):
- cache[key] = value
- while len(cache) > size:
- try:
- cache.popitem(False)
- except KeyError:
- pass
-
- def clear(self):
- cache.clear()
-
- def cache_len(self):
- return len(cache)
-
- self.get = types.MethodType(get, self)
- self.set = types.MethodType(set, self)
- self.clear = types.MethodType(clear, self)
- self.__len__ = types.MethodType(cache_len, self)
-
- else:
- class _FifoCache(object):
- def __init__(self, size):
- self.not_in_cache = not_in_cache = object()
-
- cache = {}
- key_fifo = collections.deque([], size)
-
- def get(self, key):
- return cache.get(key, not_in_cache)
-
- def set(self, key, value):
- cache[key] = value
- while len(key_fifo) > size:
- cache.pop(key_fifo.popleft(), None)
- key_fifo.append(key)
-
- def clear(self):
- cache.clear()
- key_fifo.clear()
-
- def cache_len(self):
- return len(cache)
-
- self.get = types.MethodType(get, self)
- self.set = types.MethodType(set, self)
- self.clear = types.MethodType(clear, self)
- self.__len__ = types.MethodType(cache_len, self)
-
- # argument cache for optimizing repeated calls when backtracking through recursive expressions
- packrat_cache = {} # this is set later by enabledPackrat(); this is here so that resetCache() doesn't fail
- packrat_cache_lock = RLock()
- packrat_cache_stats = [0, 0]
-
- # this method gets repeatedly called during backtracking with the same arguments -
- # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression
- def _parseCache(self, instring, loc, doActions=True, callPreParse=True):
- HIT, MISS = 0, 1
- lookup = (self, instring, loc, callPreParse, doActions)
- with ParserElement.packrat_cache_lock:
- cache = ParserElement.packrat_cache
- value = cache.get(lookup)
- if value is cache.not_in_cache:
- ParserElement.packrat_cache_stats[MISS] += 1
- try:
- value = self._parseNoCache(instring, loc, doActions, callPreParse)
- except ParseBaseException as pe:
- # cache a copy of the exception, without the traceback
- cache.set(lookup, pe.__class__(*pe.args))
- raise
- else:
- cache.set(lookup, (value[0], value[1].copy()))
- return value
- else:
- ParserElement.packrat_cache_stats[HIT] += 1
- if isinstance(value, Exception):
- raise value
- return value[0], value[1].copy()
-
- _parse = _parseNoCache
-
- @staticmethod
- def resetCache():
- ParserElement.packrat_cache.clear()
- ParserElement.packrat_cache_stats[:] = [0] * len(ParserElement.packrat_cache_stats)
-
- _packratEnabled = False
- @staticmethod
- def enablePackrat(cache_size_limit=128):
- """Enables "packrat" parsing, which adds memoizing to the parsing logic.
- Repeated parse attempts at the same string location (which happens
- often in many complex grammars) can immediately return a cached value,
- instead of re-executing parsing/validating code. Memoizing is done of
- both valid results and parsing exceptions.
-
- Parameters:
-
- - cache_size_limit - (default= ``128``) - if an integer value is provided
- will limit the size of the packrat cache; if None is passed, then
- the cache size will be unbounded; if 0 is passed, the cache will
- be effectively disabled.
-
- This speedup may break existing programs that use parse actions that
- have side-effects. For this reason, packrat parsing is disabled when
- you first import pyparsing. To activate the packrat feature, your
- program must call the class method :class:`ParserElement.enablePackrat`.
- For best results, call ``enablePackrat()`` immediately after
- importing pyparsing.
-
- Example::
-
- from pip._vendor import pyparsing
- pyparsing.ParserElement.enablePackrat()
- """
- if not ParserElement._packratEnabled:
- ParserElement._packratEnabled = True
- if cache_size_limit is None:
- ParserElement.packrat_cache = ParserElement._UnboundedCache()
- else:
- ParserElement.packrat_cache = ParserElement._FifoCache(cache_size_limit)
- ParserElement._parse = ParserElement._parseCache
-
- def parseString(self, instring, parseAll=False):
- """
- Execute the parse expression with the given string.
- This is the main interface to the client code, once the complete
- expression has been built.
-
- Returns the parsed data as a :class:`ParseResults` object, which may be
- accessed as a list, or as a dict or object with attributes if the given parser
- includes results names.
-
- If you want the grammar to require that the entire input string be
- successfully parsed, then set ``parseAll`` to True (equivalent to ending
- the grammar with ``StringEnd()``).
-
- Note: ``parseString`` implicitly calls ``expandtabs()`` on the input string,
- in order to report proper column numbers in parse actions.
- If the input string contains tabs and
- the grammar uses parse actions that use the ``loc`` argument to index into the
- string being parsed, you can ensure you have a consistent view of the input
- string by:
-
- - calling ``parseWithTabs`` on your grammar before calling ``parseString``
- (see :class:`parseWithTabs`)
- - define your parse action using the full ``(s, loc, toks)`` signature, and
- reference the input string using the parse action's ``s`` argument
- - explictly expand the tabs in your input string before calling
- ``parseString``
-
- Example::
-
- Word('a').parseString('aaaaabaaa') # -> ['aaaaa']
- Word('a').parseString('aaaaabaaa', parseAll=True) # -> Exception: Expected end of text
- """
- ParserElement.resetCache()
- if not self.streamlined:
- self.streamline()
- # ~ self.saveAsList = True
- for e in self.ignoreExprs:
- e.streamline()
- if not self.keepTabs:
- instring = instring.expandtabs()
- try:
- loc, tokens = self._parse(instring, 0)
- if parseAll:
- loc = self.preParse(instring, loc)
- se = Empty() + StringEnd()
- se._parse(instring, loc)
- except ParseBaseException as exc:
- if ParserElement.verbose_stacktrace:
- raise
- else:
- # catch and re-raise exception from here, clearing out pyparsing internal stack trace
- if getattr(exc, '__traceback__', None) is not None:
- exc.__traceback__ = self._trim_traceback(exc.__traceback__)
- raise exc
- else:
- return tokens
-
- def scanString(self, instring, maxMatches=_MAX_INT, overlap=False):
- """
- Scan the input string for expression matches. Each match will return the
- matching tokens, start location, and end location. May be called with optional
- ``maxMatches`` argument, to clip scanning after 'n' matches are found. If
- ``overlap`` is specified, then overlapping matches will be reported.
-
- Note that the start and end locations are reported relative to the string
- being parsed. See :class:`parseString` for more information on parsing
- strings with embedded tabs.
-
- Example::
-
- source = "sldjf123lsdjjkf345sldkjf879lkjsfd987"
- print(source)
- for tokens, start, end in Word(alphas).scanString(source):
- print(' '*start + '^'*(end-start))
- print(' '*start + tokens[0])
-
- prints::
-
- sldjf123lsdjjkf345sldkjf879lkjsfd987
- ^^^^^
- sldjf
- ^^^^^^^
- lsdjjkf
- ^^^^^^
- sldkjf
- ^^^^^^
- lkjsfd
- """
- if not self.streamlined:
- self.streamline()
- for e in self.ignoreExprs:
- e.streamline()
-
- if not self.keepTabs:
- instring = _ustr(instring).expandtabs()
- instrlen = len(instring)
- loc = 0
- preparseFn = self.preParse
- parseFn = self._parse
- ParserElement.resetCache()
- matches = 0
- try:
- while loc <= instrlen and matches < maxMatches:
- try:
- preloc = preparseFn(instring, loc)
- nextLoc, tokens = parseFn(instring, preloc, callPreParse=False)
- except ParseException:
- loc = preloc + 1
- else:
- if nextLoc > loc:
- matches += 1
- yield tokens, preloc, nextLoc
- if overlap:
- nextloc = preparseFn(instring, loc)
- if nextloc > loc:
- loc = nextLoc
- else:
- loc += 1
- else:
- loc = nextLoc
- else:
- loc = preloc + 1
- except ParseBaseException as exc:
- if ParserElement.verbose_stacktrace:
- raise
- else:
- # catch and re-raise exception from here, clearing out pyparsing internal stack trace
- if getattr(exc, '__traceback__', None) is not None:
- exc.__traceback__ = self._trim_traceback(exc.__traceback__)
- raise exc
-
- def transformString(self, instring):
- """
- Extension to :class:`scanString`, to modify matching text with modified tokens that may
- be returned from a parse action. To use ``transformString``, define a grammar and
- attach a parse action to it that modifies the returned token list.
- Invoking ``transformString()`` on a target string will then scan for matches,
- and replace the matched text patterns according to the logic in the parse
- action. ``transformString()`` returns the resulting transformed string.
-
- Example::
-
- wd = Word(alphas)
- wd.setParseAction(lambda toks: toks[0].title())
-
- print(wd.transformString("now is the winter of our discontent made glorious summer by this sun of york."))
-
- prints::
-
- Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York.
- """
- out = []
- lastE = 0
- # force preservation of <TAB>s, to minimize unwanted transformation of string, and to
- # keep string locs straight between transformString and scanString
- self.keepTabs = True
- try:
- for t, s, e in self.scanString(instring):
- out.append(instring[lastE:s])
- if t:
- if isinstance(t, ParseResults):
- out += t.asList()
- elif isinstance(t, list):
- out += t
- else:
- out.append(t)
- lastE = e
- out.append(instring[lastE:])
- out = [o for o in out if o]
- return "".join(map(_ustr, _flatten(out)))
- except ParseBaseException as exc:
- if ParserElement.verbose_stacktrace:
- raise
- else:
- # catch and re-raise exception from here, clearing out pyparsing internal stack trace
- if getattr(exc, '__traceback__', None) is not None:
- exc.__traceback__ = self._trim_traceback(exc.__traceback__)
- raise exc
-
- def searchString(self, instring, maxMatches=_MAX_INT):
- """
- Another extension to :class:`scanString`, simplifying the access to the tokens found
- to match the given parse expression. May be called with optional
- ``maxMatches`` argument, to clip searching after 'n' matches are found.
-
- Example::
-
- # a capitalized word starts with an uppercase letter, followed by zero or more lowercase letters
- cap_word = Word(alphas.upper(), alphas.lower())
-
- print(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity"))
-
- # the sum() builtin can be used to merge results into a single ParseResults object
- print(sum(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity")))
-
- prints::
-
- [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']]
- ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity']
- """
- try:
- return ParseResults([t for t, s, e in self.scanString(instring, maxMatches)])
- except ParseBaseException as exc:
- if ParserElement.verbose_stacktrace:
- raise
- else:
- # catch and re-raise exception from here, clearing out pyparsing internal stack trace
- if getattr(exc, '__traceback__', None) is not None:
- exc.__traceback__ = self._trim_traceback(exc.__traceback__)
- raise exc
-
- def split(self, instring, maxsplit=_MAX_INT, includeSeparators=False):
- """
- Generator method to split a string using the given expression as a separator.
- May be called with optional ``maxsplit`` argument, to limit the number of splits;
- and the optional ``includeSeparators`` argument (default= ``False``), if the separating
- matching text should be included in the split results.
-
- Example::
-
- punc = oneOf(list(".,;:/-!?"))
- print(list(punc.split("This, this?, this sentence, is badly punctuated!")))
-
- prints::
-
- ['This', ' this', '', ' this sentence', ' is badly punctuated', '']
- """
- splits = 0
- last = 0
- for t, s, e in self.scanString(instring, maxMatches=maxsplit):
- yield instring[last:s]
- if includeSeparators:
- yield t[0]
- last = e
- yield instring[last:]
-
- def __add__(self, other):
- """
- Implementation of + operator - returns :class:`And`. Adding strings to a ParserElement
- converts them to :class:`Literal`s by default.
-
- Example::
-
- greet = Word(alphas) + "," + Word(alphas) + "!"
- hello = "Hello, World!"
- print (hello, "->", greet.parseString(hello))
-
- prints::
-
- Hello, World! -> ['Hello', ',', 'World', '!']
-
- ``...`` may be used as a parse expression as a short form of :class:`SkipTo`.
-
- Literal('start') + ... + Literal('end')
-
- is equivalent to:
-
- Literal('start') + SkipTo('end')("_skipped*") + Literal('end')
-
- Note that the skipped text is returned with '_skipped' as a results name,
- and to support having multiple skips in the same parser, the value returned is
- a list of all skipped text.
- """
- if other is Ellipsis:
- return _PendingSkip(self)
-
- if isinstance(other, basestring):
- other = self._literalStringClass(other)
- if not isinstance(other, ParserElement):
- warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
- SyntaxWarning, stacklevel=2)
- return None
- return And([self, other])
-
- def __radd__(self, other):
- """
- Implementation of + operator when left operand is not a :class:`ParserElement`
- """
- if other is Ellipsis:
- return SkipTo(self)("_skipped*") + self
-
- if isinstance(other, basestring):
- other = self._literalStringClass(other)
- if not isinstance(other, ParserElement):
- warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
- SyntaxWarning, stacklevel=2)
- return None
- return other + self
-
- def __sub__(self, other):
- """
- Implementation of - operator, returns :class:`And` with error stop
- """
- if isinstance(other, basestring):
- other = self._literalStringClass(other)
- if not isinstance(other, ParserElement):
- warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
- SyntaxWarning, stacklevel=2)
- return None
- return self + And._ErrorStop() + other
-
- def __rsub__(self, other):
- """
- Implementation of - operator when left operand is not a :class:`ParserElement`
- """
- if isinstance(other, basestring):
- other = self._literalStringClass(other)
- if not isinstance(other, ParserElement):
- warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
- SyntaxWarning, stacklevel=2)
- return None
- return other - self
-
- def __mul__(self, other):
- """
- Implementation of * operator, allows use of ``expr * 3`` in place of
- ``expr + expr + expr``. Expressions may also me multiplied by a 2-integer
- tuple, similar to ``{min, max}`` multipliers in regular expressions. Tuples
- may also include ``None`` as in:
- - ``expr*(n, None)`` or ``expr*(n, )`` is equivalent
- to ``expr*n + ZeroOrMore(expr)``
- (read as "at least n instances of ``expr``")
- - ``expr*(None, n)`` is equivalent to ``expr*(0, n)``
- (read as "0 to n instances of ``expr``")
- - ``expr*(None, None)`` is equivalent to ``ZeroOrMore(expr)``
- - ``expr*(1, None)`` is equivalent to ``OneOrMore(expr)``
-
- Note that ``expr*(None, n)`` does not raise an exception if
- more than n exprs exist in the input stream; that is,
- ``expr*(None, n)`` does not enforce a maximum number of expr
- occurrences. If this behavior is desired, then write
- ``expr*(None, n) + ~expr``
- """
- if other is Ellipsis:
- other = (0, None)
- elif isinstance(other, tuple) and other[:1] == (Ellipsis,):
- other = ((0, ) + other[1:] + (None,))[:2]
-
- if isinstance(other, int):
- minElements, optElements = other, 0
- elif isinstance(other, tuple):
- other = tuple(o if o is not Ellipsis else None for o in other)
- other = (other + (None, None))[:2]
- if other[0] is None:
- other = (0, other[1])
- if isinstance(other[0], int) and other[1] is None:
- if other[0] == 0:
- return ZeroOrMore(self)
- if other[0] == 1:
- return OneOrMore(self)
- else:
- return self * other[0] + ZeroOrMore(self)
- elif isinstance(other[0], int) and isinstance(other[1], int):
- minElements, optElements = other
- optElements -= minElements
- else:
- raise TypeError("cannot multiply 'ParserElement' and ('%s', '%s') objects", type(other[0]), type(other[1]))
- else:
- raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other))
-
- if minElements < 0:
- raise ValueError("cannot multiply ParserElement by negative value")
- if optElements < 0:
- raise ValueError("second tuple value must be greater or equal to first tuple value")
- if minElements == optElements == 0:
- raise ValueError("cannot multiply ParserElement by 0 or (0, 0)")
-
- if optElements:
- def makeOptionalList(n):
- if n > 1:
- return Optional(self + makeOptionalList(n - 1))
- else:
- return Optional(self)
- if minElements:
- if minElements == 1:
- ret = self + makeOptionalList(optElements)
- else:
- ret = And([self] * minElements) + makeOptionalList(optElements)
- else:
- ret = makeOptionalList(optElements)
- else:
- if minElements == 1:
- ret = self
- else:
- ret = And([self] * minElements)
- return ret
-
- def __rmul__(self, other):
- return self.__mul__(other)
-
- def __or__(self, other):
- """
- Implementation of | operator - returns :class:`MatchFirst`
- """
- if other is Ellipsis:
- return _PendingSkip(self, must_skip=True)
-
- if isinstance(other, basestring):
- other = self._literalStringClass(other)
- if not isinstance(other, ParserElement):
- warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
- SyntaxWarning, stacklevel=2)
- return None
- return MatchFirst([self, other])
-
- def __ror__(self, other):
- """
- Implementation of | operator when left operand is not a :class:`ParserElement`
- """
- if isinstance(other, basestring):
- other = self._literalStringClass(other)
- if not isinstance(other, ParserElement):
- warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
- SyntaxWarning, stacklevel=2)
- return None
- return other | self
-
- def __xor__(self, other):
- """
- Implementation of ^ operator - returns :class:`Or`
- """
- if isinstance(other, basestring):
- other = self._literalStringClass(other)
- if not isinstance(other, ParserElement):
- warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
- SyntaxWarning, stacklevel=2)
- return None
- return Or([self, other])
-
- def __rxor__(self, other):
- """
- Implementation of ^ operator when left operand is not a :class:`ParserElement`
- """
- if isinstance(other, basestring):
- other = self._literalStringClass(other)
- if not isinstance(other, ParserElement):
- warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
- SyntaxWarning, stacklevel=2)
- return None
- return other ^ self
-
- def __and__(self, other):
- """
- Implementation of & operator - returns :class:`Each`
- """
- if isinstance(other, basestring):
- other = self._literalStringClass(other)
- if not isinstance(other, ParserElement):
- warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
- SyntaxWarning, stacklevel=2)
- return None
- return Each([self, other])
-
- def __rand__(self, other):
- """
- Implementation of & operator when left operand is not a :class:`ParserElement`
- """
- if isinstance(other, basestring):
- other = self._literalStringClass(other)
- if not isinstance(other, ParserElement):
- warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
- SyntaxWarning, stacklevel=2)
- return None
- return other & self
-
- def __invert__(self):
- """
- Implementation of ~ operator - returns :class:`NotAny`
- """
- return NotAny(self)
-
- def __iter__(self):
- # must implement __iter__ to override legacy use of sequential access to __getitem__ to
- # iterate over a sequence
- raise TypeError('%r object is not iterable' % self.__class__.__name__)
-
- def __getitem__(self, key):
- """
- use ``[]`` indexing notation as a short form for expression repetition:
- - ``expr[n]`` is equivalent to ``expr*n``
- - ``expr[m, n]`` is equivalent to ``expr*(m, n)``
- - ``expr[n, ...]`` or ``expr[n,]`` is equivalent
- to ``expr*n + ZeroOrMore(expr)``
- (read as "at least n instances of ``expr``")
- - ``expr[..., n]`` is equivalent to ``expr*(0, n)``
- (read as "0 to n instances of ``expr``")
- - ``expr[...]`` and ``expr[0, ...]`` are equivalent to ``ZeroOrMore(expr)``
- - ``expr[1, ...]`` is equivalent to ``OneOrMore(expr)``
- ``None`` may be used in place of ``...``.
-
- Note that ``expr[..., n]`` and ``expr[m, n]``do not raise an exception
- if more than ``n`` ``expr``s exist in the input stream. If this behavior is
- desired, then write ``expr[..., n] + ~expr``.
- """
-
- # convert single arg keys to tuples
- try:
- if isinstance(key, str):
- key = (key,)
- iter(key)
- except TypeError:
- key = (key, key)
-
- if len(key) > 2:
- warnings.warn("only 1 or 2 index arguments supported ({0}{1})".format(key[:5],
- '... [{0}]'.format(len(key))
- if len(key) > 5 else ''))
-
- # clip to 2 elements
- ret = self * tuple(key[:2])
- return ret
-
- def __call__(self, name=None):
- """
- Shortcut for :class:`setResultsName`, with ``listAllMatches=False``.
-
- If ``name`` is given with a trailing ``'*'`` character, then ``listAllMatches`` will be
- passed as ``True``.
-
- If ``name` is omitted, same as calling :class:`copy`.
-
- Example::
-
- # these are equivalent
- userdata = Word(alphas).setResultsName("name") + Word(nums + "-").setResultsName("socsecno")
- userdata = Word(alphas)("name") + Word(nums + "-")("socsecno")
- """
- if name is not None:
- return self._setResultsName(name)
- else:
- return self.copy()
-
- def suppress(self):
- """
- Suppresses the output of this :class:`ParserElement`; useful to keep punctuation from
- cluttering up returned output.
- """
- return Suppress(self)
-
- def leaveWhitespace(self):
- """
- Disables the skipping of whitespace before matching the characters in the
- :class:`ParserElement`'s defined pattern. This is normally only used internally by
- the pyparsing module, but may be needed in some whitespace-sensitive grammars.
- """
- self.skipWhitespace = False
- return self
-
- def setWhitespaceChars(self, chars):
- """
- Overrides the default whitespace chars
- """
- self.skipWhitespace = True
- self.whiteChars = chars
- self.copyDefaultWhiteChars = False
- return self
-
- def parseWithTabs(self):
- """
- Overrides default behavior to expand ``<TAB>``s to spaces before parsing the input string.
- Must be called before ``parseString`` when the input grammar contains elements that
- match ``<TAB>`` characters.
- """
- self.keepTabs = True
- return self
-
- def ignore(self, other):
- """
- Define expression to be ignored (e.g., comments) while doing pattern
- matching; may be called repeatedly, to define multiple comment or other
- ignorable patterns.
-
- Example::
-
- patt = OneOrMore(Word(alphas))
- patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj']
-
- patt.ignore(cStyleComment)
- patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj', 'lskjd']
- """
- if isinstance(other, basestring):
- other = Suppress(other)
-
- if isinstance(other, Suppress):
- if other not in self.ignoreExprs:
- self.ignoreExprs.append(other)
- else:
- self.ignoreExprs.append(Suppress(other.copy()))
- return self
-
- def setDebugActions(self, startAction, successAction, exceptionAction):
- """
- Enable display of debugging messages while doing pattern matching.
- """
- self.debugActions = (startAction or _defaultStartDebugAction,
- successAction or _defaultSuccessDebugAction,
- exceptionAction or _defaultExceptionDebugAction)
- self.debug = True
- return self
-
- def setDebug(self, flag=True):
- """
- Enable display of debugging messages while doing pattern matching.
- Set ``flag`` to True to enable, False to disable.
-
- Example::
-
- wd = Word(alphas).setName("alphaword")
- integer = Word(nums).setName("numword")
- term = wd | integer
-
- # turn on debugging for wd
- wd.setDebug()
-
- OneOrMore(term).parseString("abc 123 xyz 890")
-
- prints::
-
- Match alphaword at loc 0(1,1)
- Matched alphaword -> ['abc']
- Match alphaword at loc 3(1,4)
- Exception raised:Expected alphaword (at char 4), (line:1, col:5)
- Match alphaword at loc 7(1,8)
- Matched alphaword -> ['xyz']
- Match alphaword at loc 11(1,12)
- Exception raised:Expected alphaword (at char 12), (line:1, col:13)
- Match alphaword at loc 15(1,16)
- Exception raised:Expected alphaword (at char 15), (line:1, col:16)
-
- The output shown is that produced by the default debug actions - custom debug actions can be
- specified using :class:`setDebugActions`. Prior to attempting
- to match the ``wd`` expression, the debugging message ``"Match <exprname> at loc <n>(<line>,<col>)"``
- is shown. Then if the parse succeeds, a ``"Matched"`` message is shown, or an ``"Exception raised"``
- message is shown. Also note the use of :class:`setName` to assign a human-readable name to the expression,
- which makes debugging and exception messages easier to understand - for instance, the default
- name created for the :class:`Word` expression without calling ``setName`` is ``"W:(ABCD...)"``.
- """
- if flag:
- self.setDebugActions(_defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction)
- else:
- self.debug = False
- return self
-
- def __str__(self):
- return self.name
-
- def __repr__(self):
- return _ustr(self)
-
- def streamline(self):
- self.streamlined = True
- self.strRepr = None
- return self
-
- def checkRecursion(self, parseElementList):
- pass
-
- def validate(self, validateTrace=None):
- """
- Check defined expressions for valid structure, check for infinite recursive definitions.
- """
- self.checkRecursion([])
-
- def parseFile(self, file_or_filename, parseAll=False):
- """
- Execute the parse expression on the given file or filename.
- If a filename is specified (instead of a file object),
- the entire file is opened, read, and closed before parsing.
- """
- try:
- file_contents = file_or_filename.read()
- except AttributeError:
- with open(file_or_filename, "r") as f:
- file_contents = f.read()
- try:
- return self.parseString(file_contents, parseAll)
- except ParseBaseException as exc:
- if ParserElement.verbose_stacktrace:
- raise
- else:
- # catch and re-raise exception from here, clearing out pyparsing internal stack trace
- if getattr(exc, '__traceback__', None) is not None:
- exc.__traceback__ = self._trim_traceback(exc.__traceback__)
- raise exc
-
- def __eq__(self, other):
- if self is other:
- return True
- elif isinstance(other, basestring):
- return self.matches(other)
- elif isinstance(other, ParserElement):
- return vars(self) == vars(other)
- return False
-
- def __ne__(self, other):
- return not (self == other)
-
- def __hash__(self):
- return id(self)
-
- def __req__(self, other):
- return self == other
-
- def __rne__(self, other):
- return not (self == other)
-
- def matches(self, testString, parseAll=True):
- """
- Method for quick testing of a parser against a test string. Good for simple
- inline microtests of sub expressions while building up larger parser.
-
- Parameters:
- - testString - to test against this expression for a match
- - parseAll - (default= ``True``) - flag to pass to :class:`parseString` when running tests
-
- Example::
-
- expr = Word(nums)
- assert expr.matches("100")
- """
- try:
- self.parseString(_ustr(testString), parseAll=parseAll)
- return True
- except ParseBaseException:
- return False
-
- def runTests(self, tests, parseAll=True, comment='#',
- fullDump=True, printResults=True, failureTests=False, postParse=None,
- file=None):
- """
- Execute the parse expression on a series of test strings, showing each
- test, the parsed results or where the parse failed. Quick and easy way to
- run a parse expression against a list of sample strings.
-
- Parameters:
- - tests - a list of separate test strings, or a multiline string of test strings
- - parseAll - (default= ``True``) - flag to pass to :class:`parseString` when running tests
- - comment - (default= ``'#'``) - expression for indicating embedded comments in the test
- string; pass None to disable comment filtering
- - fullDump - (default= ``True``) - dump results as list followed by results names in nested outline;
- if False, only dump nested list
- - printResults - (default= ``True``) prints test output to stdout
- - failureTests - (default= ``False``) indicates if these tests are expected to fail parsing
- - postParse - (default= ``None``) optional callback for successful parse results; called as
- `fn(test_string, parse_results)` and returns a string to be added to the test output
- - file - (default=``None``) optional file-like object to which test output will be written;
- if None, will default to ``sys.stdout``
-
- Returns: a (success, results) tuple, where success indicates that all tests succeeded
- (or failed if ``failureTests`` is True), and the results contain a list of lines of each
- test's output
-
- Example::
-
- number_expr = pyparsing_common.number.copy()
-
- result = number_expr.runTests('''
- # unsigned integer
- 100
- # negative integer
- -100
- # float with scientific notation
- 6.02e23
- # integer with scientific notation
- 1e-12
- ''')
- print("Success" if result[0] else "Failed!")
-
- result = number_expr.runTests('''
- # stray character
- 100Z
- # missing leading digit before '.'
- -.100
- # too many '.'
- 3.14.159
- ''', failureTests=True)
- print("Success" if result[0] else "Failed!")
-
- prints::
-
- # unsigned integer
- 100
- [100]
-
- # negative integer
- -100
- [-100]
-
- # float with scientific notation
- 6.02e23
- [6.02e+23]
-
- # integer with scientific notation
- 1e-12
- [1e-12]
-
- Success
-
- # stray character
- 100Z
- ^
- FAIL: Expected end of text (at char 3), (line:1, col:4)
-
- # missing leading digit before '.'
- -.100
- ^
- FAIL: Expected {real number with scientific notation | real number | signed integer} (at char 0), (line:1, col:1)
-
- # too many '.'
- 3.14.159
- ^
- FAIL: Expected end of text (at char 4), (line:1, col:5)
-
- Success
-
- Each test string must be on a single line. If you want to test a string that spans multiple
- lines, create a test like this::
-
- expr.runTest(r"this is a test\\n of strings that spans \\n 3 lines")
-
- (Note that this is a raw string literal, you must include the leading 'r'.)
- """
- if isinstance(tests, basestring):
- tests = list(map(str.strip, tests.rstrip().splitlines()))
- if isinstance(comment, basestring):
- comment = Literal(comment)
- if file is None:
- file = sys.stdout
- print_ = file.write
-
- allResults = []
- comments = []
- success = True
- NL = Literal(r'\n').addParseAction(replaceWith('\n')).ignore(quotedString)
- BOM = u'\ufeff'
- for t in tests:
- if comment is not None and comment.matches(t, False) or comments and not t:
- comments.append(t)
- continue
- if not t:
- continue
- out = ['\n' + '\n'.join(comments) if comments else '', t]
- comments = []
- try:
- # convert newline marks to actual newlines, and strip leading BOM if present
- t = NL.transformString(t.lstrip(BOM))
- result = self.parseString(t, parseAll=parseAll)
- except ParseBaseException as pe:
- fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else ""
- if '\n' in t:
- out.append(line(pe.loc, t))
- out.append(' ' * (col(pe.loc, t) - 1) + '^' + fatal)
- else:
- out.append(' ' * pe.loc + '^' + fatal)
- out.append("FAIL: " + str(pe))
- success = success and failureTests
- result = pe
- except Exception as exc:
- out.append("FAIL-EXCEPTION: " + str(exc))
- success = success and failureTests
- result = exc
- else:
- success = success and not failureTests
- if postParse is not None:
- try:
- pp_value = postParse(t, result)
- if pp_value is not None:
- if isinstance(pp_value, ParseResults):
- out.append(pp_value.dump())
- else:
- out.append(str(pp_value))
- else:
- out.append(result.dump())
- except Exception as e:
- out.append(result.dump(full=fullDump))
- out.append("{0} failed: {1}: {2}".format(postParse.__name__, type(e).__name__, e))
- else:
- out.append(result.dump(full=fullDump))
-
- if printResults:
- if fullDump:
- out.append('')
- print_('\n'.join(out))
-
- allResults.append((t, result))
-
- return success, allResults
-
-
-class _PendingSkip(ParserElement):
- # internal placeholder class to hold a place were '...' is added to a parser element,
- # once another ParserElement is added, this placeholder will be replaced with a SkipTo
- def __init__(self, expr, must_skip=False):
- super(_PendingSkip, self).__init__()
- self.strRepr = str(expr + Empty()).replace('Empty', '...')
- self.name = self.strRepr
- self.anchor = expr
- self.must_skip = must_skip
-
- def __add__(self, other):
- skipper = SkipTo(other).setName("...")("_skipped*")
- if self.must_skip:
- def must_skip(t):
- if not t._skipped or t._skipped.asList() == ['']:
- del t[0]
- t.pop("_skipped", None)
- def show_skip(t):
- if t._skipped.asList()[-1:] == ['']:
- skipped = t.pop('_skipped')
- t['_skipped'] = 'missing <' + repr(self.anchor) + '>'
- return (self.anchor + skipper().addParseAction(must_skip)
- | skipper().addParseAction(show_skip)) + other
-
- return self.anchor + skipper + other
-
- def __repr__(self):
- return self.strRepr
-
- def parseImpl(self, *args):
- raise Exception("use of `...` expression without following SkipTo target expression")
-
-
-class Token(ParserElement):
- """Abstract :class:`ParserElement` subclass, for defining atomic
- matching patterns.
- """
- def __init__(self):
- super(Token, self).__init__(savelist=False)
-
-
-class Empty(Token):
- """An empty token, will always match.
- """
- def __init__(self):
- super(Empty, self).__init__()
- self.name = "Empty"
- self.mayReturnEmpty = True
- self.mayIndexError = False
-
-
-class NoMatch(Token):
- """A token that will never match.
- """
- def __init__(self):
- super(NoMatch, self).__init__()
- self.name = "NoMatch"
- self.mayReturnEmpty = True
- self.mayIndexError = False
- self.errmsg = "Unmatchable token"
-
- def parseImpl(self, instring, loc, doActions=True):
- raise ParseException(instring, loc, self.errmsg, self)
-
-
-class Literal(Token):
- """Token to exactly match a specified string.
-
- Example::
-
- Literal('blah').parseString('blah') # -> ['blah']
- Literal('blah').parseString('blahfooblah') # -> ['blah']
- Literal('blah').parseString('bla') # -> Exception: Expected "blah"
-
- For case-insensitive matching, use :class:`CaselessLiteral`.
-
- For keyword matching (force word break before and after the matched string),
- use :class:`Keyword` or :class:`CaselessKeyword`.
- """
- def __init__(self, matchString):
- super(Literal, self).__init__()
- self.match = matchString
- self.matchLen = len(matchString)
- try:
- self.firstMatchChar = matchString[0]
- except IndexError:
- warnings.warn("null string passed to Literal; use Empty() instead",
- SyntaxWarning, stacklevel=2)
- self.__class__ = Empty
- self.name = '"%s"' % _ustr(self.match)
- self.errmsg = "Expected " + self.name
- self.mayReturnEmpty = False
- self.mayIndexError = False
-
- # Performance tuning: modify __class__ to select
- # a parseImpl optimized for single-character check
- if self.matchLen == 1 and type(self) is Literal:
- self.__class__ = _SingleCharLiteral
-
- def parseImpl(self, instring, loc, doActions=True):
- if instring[loc] == self.firstMatchChar and instring.startswith(self.match, loc):
- return loc + self.matchLen, self.match
- raise ParseException(instring, loc, self.errmsg, self)
-
-class _SingleCharLiteral(Literal):
- def parseImpl(self, instring, loc, doActions=True):
- if instring[loc] == self.firstMatchChar:
- return loc + 1, self.match
- raise ParseException(instring, loc, self.errmsg, self)
-
-_L = Literal
-ParserElement._literalStringClass = Literal
-
-class Keyword(Token):
- """Token to exactly match a specified string as a keyword, that is,
- it must be immediately followed by a non-keyword character. Compare
- with :class:`Literal`:
-
- - ``Literal("if")`` will match the leading ``'if'`` in
- ``'ifAndOnlyIf'``.
- - ``Keyword("if")`` will not; it will only match the leading
- ``'if'`` in ``'if x=1'``, or ``'if(y==2)'``
-
- Accepts two optional constructor arguments in addition to the
- keyword string:
-
- - ``identChars`` is a string of characters that would be valid
- identifier characters, defaulting to all alphanumerics + "_" and
- "$"
- - ``caseless`` allows case-insensitive matching, default is ``False``.
-
- Example::
-
- Keyword("start").parseString("start") # -> ['start']
- Keyword("start").parseString("starting") # -> Exception
-
- For case-insensitive matching, use :class:`CaselessKeyword`.
- """
- DEFAULT_KEYWORD_CHARS = alphanums + "_$"
-
- def __init__(self, matchString, identChars=None, caseless=False):
- super(Keyword, self).__init__()
- if identChars is None:
- identChars = Keyword.DEFAULT_KEYWORD_CHARS
- self.match = matchString
- self.matchLen = len(matchString)
- try:
- self.firstMatchChar = matchString[0]
- except IndexError:
- warnings.warn("null string passed to Keyword; use Empty() instead",
- SyntaxWarning, stacklevel=2)
- self.name = '"%s"' % self.match
- self.errmsg = "Expected " + self.name
- self.mayReturnEmpty = False
- self.mayIndexError = False
- self.caseless = caseless
- if caseless:
- self.caselessmatch = matchString.upper()
- identChars = identChars.upper()
- self.identChars = set(identChars)
-
- def parseImpl(self, instring, loc, doActions=True):
- if self.caseless:
- if ((instring[loc:loc + self.matchLen].upper() == self.caselessmatch)
- and (loc >= len(instring) - self.matchLen
- or instring[loc + self.matchLen].upper() not in self.identChars)
- and (loc == 0
- or instring[loc - 1].upper() not in self.identChars)):
- return loc + self.matchLen, self.match
-
- else:
- if instring[loc] == self.firstMatchChar:
- if ((self.matchLen == 1 or instring.startswith(self.match, loc))
- and (loc >= len(instring) - self.matchLen
- or instring[loc + self.matchLen] not in self.identChars)
- and (loc == 0 or instring[loc - 1] not in self.identChars)):
- return loc + self.matchLen, self.match
-
- raise ParseException(instring, loc, self.errmsg, self)
-
- def copy(self):
- c = super(Keyword, self).copy()
- c.identChars = Keyword.DEFAULT_KEYWORD_CHARS
- return c
-
- @staticmethod
- def setDefaultKeywordChars(chars):
- """Overrides the default Keyword chars
- """
- Keyword.DEFAULT_KEYWORD_CHARS = chars
-
-class CaselessLiteral(Literal):
- """Token to match a specified string, ignoring case of letters.
- Note: the matched results will always be in the case of the given
- match string, NOT the case of the input text.
-
- Example::
-
- OneOrMore(CaselessLiteral("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD', 'CMD']
-
- (Contrast with example for :class:`CaselessKeyword`.)
- """
- def __init__(self, matchString):
- super(CaselessLiteral, self).__init__(matchString.upper())
- # Preserve the defining literal.
- self.returnString = matchString
- self.name = "'%s'" % self.returnString
- self.errmsg = "Expected " + self.name
-
- def parseImpl(self, instring, loc, doActions=True):
- if instring[loc:loc + self.matchLen].upper() == self.match:
- return loc + self.matchLen, self.returnString
- raise ParseException(instring, loc, self.errmsg, self)
-
-class CaselessKeyword(Keyword):
- """
- Caseless version of :class:`Keyword`.
-
- Example::
-
- OneOrMore(CaselessKeyword("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD']
-
- (Contrast with example for :class:`CaselessLiteral`.)
- """
- def __init__(self, matchString, identChars=None):
- super(CaselessKeyword, self).__init__(matchString, identChars, caseless=True)
-
-class CloseMatch(Token):
- """A variation on :class:`Literal` which matches "close" matches,
- that is, strings with at most 'n' mismatching characters.
- :class:`CloseMatch` takes parameters:
-
- - ``match_string`` - string to be matched
- - ``maxMismatches`` - (``default=1``) maximum number of
- mismatches allowed to count as a match
-
- The results from a successful parse will contain the matched text
- from the input string and the following named results:
-
- - ``mismatches`` - a list of the positions within the
- match_string where mismatches were found
- - ``original`` - the original match_string used to compare
- against the input string
-
- If ``mismatches`` is an empty list, then the match was an exact
- match.
-
- Example::
-
- patt = CloseMatch("ATCATCGAATGGA")
- patt.parseString("ATCATCGAAXGGA") # -> (['ATCATCGAAXGGA'], {'mismatches': [[9]], 'original': ['ATCATCGAATGGA']})
- patt.parseString("ATCAXCGAAXGGA") # -> Exception: Expected 'ATCATCGAATGGA' (with up to 1 mismatches) (at char 0), (line:1, col:1)
-
- # exact match
- patt.parseString("ATCATCGAATGGA") # -> (['ATCATCGAATGGA'], {'mismatches': [[]], 'original': ['ATCATCGAATGGA']})
-
- # close match allowing up to 2 mismatches
- patt = CloseMatch("ATCATCGAATGGA", maxMismatches=2)
- patt.parseString("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']})
- """
- def __init__(self, match_string, maxMismatches=1):
- super(CloseMatch, self).__init__()
- self.name = match_string
- self.match_string = match_string
- self.maxMismatches = maxMismatches
- self.errmsg = "Expected %r (with up to %d mismatches)" % (self.match_string, self.maxMismatches)
- self.mayIndexError = False
- self.mayReturnEmpty = False
-
- def parseImpl(self, instring, loc, doActions=True):
- start = loc
- instrlen = len(instring)
- maxloc = start + len(self.match_string)
-
- if maxloc <= instrlen:
- match_string = self.match_string
- match_stringloc = 0
- mismatches = []
- maxMismatches = self.maxMismatches
-
- for match_stringloc, s_m in enumerate(zip(instring[loc:maxloc], match_string)):
- src, mat = s_m
- if src != mat:
- mismatches.append(match_stringloc)
- if len(mismatches) > maxMismatches:
- break
- else:
- loc = match_stringloc + 1
- results = ParseResults([instring[start:loc]])
- results['original'] = match_string
- results['mismatches'] = mismatches
- return loc, results
-
- raise ParseException(instring, loc, self.errmsg, self)
-
-
-class Word(Token):
- """Token for matching words composed of allowed character sets.
- Defined with string containing all allowed initial characters, an
- optional string containing allowed body characters (if omitted,
- defaults to the initial character set), and an optional minimum,
- maximum, and/or exact length. The default value for ``min`` is
- 1 (a minimum value < 1 is not valid); the default values for
- ``max`` and ``exact`` are 0, meaning no maximum or exact
- length restriction. An optional ``excludeChars`` parameter can
- list characters that might be found in the input ``bodyChars``
- string; useful to define a word of all printables except for one or
- two characters, for instance.
-
- :class:`srange` is useful for defining custom character set strings
- for defining ``Word`` expressions, using range notation from
- regular expression character sets.
-
- A common mistake is to use :class:`Word` to match a specific literal
- string, as in ``Word("Address")``. Remember that :class:`Word`
- uses the string argument to define *sets* of matchable characters.
- This expression would match "Add", "AAA", "dAred", or any other word
- made up of the characters 'A', 'd', 'r', 'e', and 's'. To match an
- exact literal string, use :class:`Literal` or :class:`Keyword`.
-
- pyparsing includes helper strings for building Words:
-
- - :class:`alphas`
- - :class:`nums`
- - :class:`alphanums`
- - :class:`hexnums`
- - :class:`alphas8bit` (alphabetic characters in ASCII range 128-255
- - accented, tilded, umlauted, etc.)
- - :class:`punc8bit` (non-alphabetic characters in ASCII range
- 128-255 - currency, symbols, superscripts, diacriticals, etc.)
- - :class:`printables` (any non-whitespace character)
-
- Example::
-
- # a word composed of digits
- integer = Word(nums) # equivalent to Word("0123456789") or Word(srange("0-9"))
-
- # a word with a leading capital, and zero or more lowercase
- capital_word = Word(alphas.upper(), alphas.lower())
-
- # hostnames are alphanumeric, with leading alpha, and '-'
- hostname = Word(alphas, alphanums + '-')
-
- # roman numeral (not a strict parser, accepts invalid mix of characters)
- roman = Word("IVXLCDM")
-
- # any string of non-whitespace characters, except for ','
- csv_value = Word(printables, excludeChars=",")
- """
- def __init__(self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None):
- super(Word, self).__init__()
- if excludeChars:
- excludeChars = set(excludeChars)
- initChars = ''.join(c for c in initChars if c not in excludeChars)
- if bodyChars:
- bodyChars = ''.join(c for c in bodyChars if c not in excludeChars)
- self.initCharsOrig = initChars
- self.initChars = set(initChars)
- if bodyChars:
- self.bodyCharsOrig = bodyChars
- self.bodyChars = set(bodyChars)
- else:
- self.bodyCharsOrig = initChars
- self.bodyChars = set(initChars)
-
- self.maxSpecified = max > 0
-
- if min < 1:
- raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted")
-
- self.minLen = min
-
- if max > 0:
- self.maxLen = max
- else:
- self.maxLen = _MAX_INT
-
- if exact > 0:
- self.maxLen = exact
- self.minLen = exact
-
- self.name = _ustr(self)
- self.errmsg = "Expected " + self.name
- self.mayIndexError = False
- self.asKeyword = asKeyword
-
- if ' ' not in self.initCharsOrig + self.bodyCharsOrig and (min == 1 and max == 0 and exact == 0):
- if self.bodyCharsOrig == self.initCharsOrig:
- self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig)
- elif len(self.initCharsOrig) == 1:
- self.reString = "%s[%s]*" % (re.escape(self.initCharsOrig),
- _escapeRegexRangeChars(self.bodyCharsOrig),)
- else:
- self.reString = "[%s][%s]*" % (_escapeRegexRangeChars(self.initCharsOrig),
- _escapeRegexRangeChars(self.bodyCharsOrig),)
- if self.asKeyword:
- self.reString = r"\b" + self.reString + r"\b"
-
- try:
- self.re = re.compile(self.reString)
- except Exception:
- self.re = None
- else:
- self.re_match = self.re.match
- self.__class__ = _WordRegex
-
- def parseImpl(self, instring, loc, doActions=True):
- if instring[loc] not in self.initChars:
- raise ParseException(instring, loc, self.errmsg, self)
-
- start = loc
- loc += 1
- instrlen = len(instring)
- bodychars = self.bodyChars
- maxloc = start + self.maxLen
- maxloc = min(maxloc, instrlen)
- while loc < maxloc and instring[loc] in bodychars:
- loc += 1
-
- throwException = False
- if loc - start < self.minLen:
- throwException = True
- elif self.maxSpecified and loc < instrlen and instring[loc] in bodychars:
- throwException = True
- elif self.asKeyword:
- if (start > 0 and instring[start - 1] in bodychars
- or loc < instrlen and instring[loc] in bodychars):
- throwException = True
-
- if throwException:
- raise ParseException(instring, loc, self.errmsg, self)
-
- return loc, instring[start:loc]
-
- def __str__(self):
- try:
- return super(Word, self).__str__()
- except Exception:
- pass
-
- if self.strRepr is None:
-
- def charsAsStr(s):
- if len(s) > 4:
- return s[:4] + "..."
- else:
- return s
-
- if self.initCharsOrig != self.bodyCharsOrig:
- self.strRepr = "W:(%s, %s)" % (charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig))
- else:
- self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig)
-
- return self.strRepr
-
-class _WordRegex(Word):
- def parseImpl(self, instring, loc, doActions=True):
- result = self.re_match(instring, loc)
- if not result:
- raise ParseException(instring, loc, self.errmsg, self)
-
- loc = result.end()
- return loc, result.group()
-
-
-class Char(_WordRegex):
- """A short-cut class for defining ``Word(characters, exact=1)``,
- when defining a match of any single character in a string of
- characters.
- """
- def __init__(self, charset, asKeyword=False, excludeChars=None):
- super(Char, self).__init__(charset, exact=1, asKeyword=asKeyword, excludeChars=excludeChars)
- self.reString = "[%s]" % _escapeRegexRangeChars(''.join(self.initChars))
- if asKeyword:
- self.reString = r"\b%s\b" % self.reString
- self.re = re.compile(self.reString)
- self.re_match = self.re.match
-
-
-class Regex(Token):
- r"""Token for matching strings that match a given regular
- expression. Defined with string specifying the regular expression in
- a form recognized by the stdlib Python `re module <https://docs.python.org/3/library/re.html>`_.
- If the given regex contains named groups (defined using ``(?P<name>...)``),
- these will be preserved as named parse results.
-
- If instead of the Python stdlib re module you wish to use a different RE module
- (such as the `regex` module), you can replace it by either building your
- Regex object with a compiled RE that was compiled using regex:
-
- Example::
-
- realnum = Regex(r"[+-]?\d+\.\d*")
- date = Regex(r'(?P<year>\d{4})-(?P<month>\d\d?)-(?P<day>\d\d?)')
- # ref: https://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression
- roman = Regex(r"M{0,4}(CM|CD|D?{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})")
-
- # use regex module instead of stdlib re module to construct a Regex using
- # a compiled regular expression
- import regex
- parser = pp.Regex(regex.compile(r'[0-9]'))
-
- """
- def __init__(self, pattern, flags=0, asGroupList=False, asMatch=False):
- """The parameters ``pattern`` and ``flags`` are passed
- to the ``re.compile()`` function as-is. See the Python
- `re module <https://docs.python.org/3/library/re.html>`_ module for an
- explanation of the acceptable patterns and flags.
- """
- super(Regex, self).__init__()
-
- if isinstance(pattern, basestring):
- if not pattern:
- warnings.warn("null string passed to Regex; use Empty() instead",
- SyntaxWarning, stacklevel=2)
-
- self.pattern = pattern
- self.flags = flags
-
- try:
- self.re = re.compile(self.pattern, self.flags)
- self.reString = self.pattern
- except sre_constants.error:
- warnings.warn("invalid pattern (%s) passed to Regex" % pattern,
- SyntaxWarning, stacklevel=2)
- raise
-
- elif hasattr(pattern, 'pattern') and hasattr(pattern, 'match'):
- self.re = pattern
- self.pattern = self.reString = pattern.pattern
- self.flags = flags
-
- else:
- raise TypeError("Regex may only be constructed with a string or a compiled RE object")
-
- self.re_match = self.re.match
-
- self.name = _ustr(self)
- self.errmsg = "Expected " + self.name
- self.mayIndexError = False
- self.mayReturnEmpty = self.re_match("") is not None
- self.asGroupList = asGroupList
- self.asMatch = asMatch
- if self.asGroupList:
- self.parseImpl = self.parseImplAsGroupList
- if self.asMatch:
- self.parseImpl = self.parseImplAsMatch
-
- def parseImpl(self, instring, loc, doActions=True):
- result = self.re_match(instring, loc)
- if not result:
- raise ParseException(instring, loc, self.errmsg, self)
-
- loc = result.end()
- ret = ParseResults(result.group())
- d = result.groupdict()
- if d:
- for k, v in d.items():
- ret[k] = v
- return loc, ret
-
- def parseImplAsGroupList(self, instring, loc, doActions=True):
- result = self.re_match(instring, loc)
- if not result:
- raise ParseException(instring, loc, self.errmsg, self)
-
- loc = result.end()
- ret = result.groups()
- return loc, ret
-
- def parseImplAsMatch(self, instring, loc, doActions=True):
- result = self.re_match(instring, loc)
- if not result:
- raise ParseException(instring, loc, self.errmsg, self)
-
- loc = result.end()
- ret = result
- return loc, ret
-
- def __str__(self):
- try:
- return super(Regex, self).__str__()
- except Exception:
- pass
-
- if self.strRepr is None:
- self.strRepr = "Re:(%s)" % repr(self.pattern)
-
- return self.strRepr
-
- def sub(self, repl):
- r"""
- Return Regex with an attached parse action to transform the parsed
- result as if called using `re.sub(expr, repl, string) <https://docs.python.org/3/library/re.html#re.sub>`_.
-
- Example::
-
- make_html = Regex(r"(\w+):(.*?):").sub(r"<\1>\2</\1>")
- print(make_html.transformString("h1:main title:"))
- # prints "<h1>main title</h1>"
- """
- if self.asGroupList:
- warnings.warn("cannot use sub() with Regex(asGroupList=True)",
- SyntaxWarning, stacklevel=2)
- raise SyntaxError()
-
- if self.asMatch and callable(repl):
- warnings.warn("cannot use sub() with a callable with Regex(asMatch=True)",
- SyntaxWarning, stacklevel=2)
- raise SyntaxError()
-
- if self.asMatch:
- def pa(tokens):
- return tokens[0].expand(repl)
- else:
- def pa(tokens):
- return self.re.sub(repl, tokens[0])
- return self.addParseAction(pa)
-
-class QuotedString(Token):
- r"""
- Token for matching strings that are delimited by quoting characters.
-
- Defined with the following parameters:
-
- - quoteChar - string of one or more characters defining the
- quote delimiting string
- - escChar - character to escape quotes, typically backslash
- (default= ``None``)
- - escQuote - special quote sequence to escape an embedded quote
- string (such as SQL's ``""`` to escape an embedded ``"``)
- (default= ``None``)
- - multiline - boolean indicating whether quotes can span
- multiple lines (default= ``False``)
- - unquoteResults - boolean indicating whether the matched text
- should be unquoted (default= ``True``)
- - endQuoteChar - string of one or more characters defining the
- end of the quote delimited string (default= ``None`` => same as
- quoteChar)
- - convertWhitespaceEscapes - convert escaped whitespace
- (``'\t'``, ``'\n'``, etc.) to actual whitespace
- (default= ``True``)
-
- Example::
-
- qs = QuotedString('"')
- print(qs.searchString('lsjdf "This is the quote" sldjf'))
- complex_qs = QuotedString('{{', endQuoteChar='}}')
- print(complex_qs.searchString('lsjdf {{This is the "quote"}} sldjf'))
- sql_qs = QuotedString('"', escQuote='""')
- print(sql_qs.searchString('lsjdf "This is the quote with ""embedded"" quotes" sldjf'))
-
- prints::
-
- [['This is the quote']]
- [['This is the "quote"']]
- [['This is the quote with "embedded" quotes']]
- """
- def __init__(self, quoteChar, escChar=None, escQuote=None, multiline=False,
- unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True):
- super(QuotedString, self).__init__()
-
- # remove white space from quote chars - wont work anyway
- quoteChar = quoteChar.strip()
- if not quoteChar:
- warnings.warn("quoteChar cannot be the empty string", SyntaxWarning, stacklevel=2)
- raise SyntaxError()
-
- if endQuoteChar is None:
- endQuoteChar = quoteChar
- else:
- endQuoteChar = endQuoteChar.strip()
- if not endQuoteChar:
- warnings.warn("endQuoteChar cannot be the empty string", SyntaxWarning, stacklevel=2)
- raise SyntaxError()
-
- self.quoteChar = quoteChar
- self.quoteCharLen = len(quoteChar)
- self.firstQuoteChar = quoteChar[0]
- self.endQuoteChar = endQuoteChar
- self.endQuoteCharLen = len(endQuoteChar)
- self.escChar = escChar
- self.escQuote = escQuote
- self.unquoteResults = unquoteResults
- self.convertWhitespaceEscapes = convertWhitespaceEscapes
-
- if multiline:
- self.flags = re.MULTILINE | re.DOTALL
- self.pattern = r'%s(?:[^%s%s]' % (re.escape(self.quoteChar),
- _escapeRegexRangeChars(self.endQuoteChar[0]),
- (escChar is not None and _escapeRegexRangeChars(escChar) or ''))
- else:
- self.flags = 0
- self.pattern = r'%s(?:[^%s\n\r%s]' % (re.escape(self.quoteChar),
- _escapeRegexRangeChars(self.endQuoteChar[0]),
- (escChar is not None and _escapeRegexRangeChars(escChar) or ''))
- if len(self.endQuoteChar) > 1:
- self.pattern += (
- '|(?:' + ')|(?:'.join("%s[^%s]" % (re.escape(self.endQuoteChar[:i]),
- _escapeRegexRangeChars(self.endQuoteChar[i]))
- for i in range(len(self.endQuoteChar) - 1, 0, -1)) + ')')
-
- if escQuote:
- self.pattern += (r'|(?:%s)' % re.escape(escQuote))
- if escChar:
- self.pattern += (r'|(?:%s.)' % re.escape(escChar))
- self.escCharReplacePattern = re.escape(self.escChar) + "(.)"
- self.pattern += (r')*%s' % re.escape(self.endQuoteChar))
-
- try:
- self.re = re.compile(self.pattern, self.flags)
- self.reString = self.pattern
- self.re_match = self.re.match
- except sre_constants.error:
- warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern,
- SyntaxWarning, stacklevel=2)
- raise
-
- self.name = _ustr(self)
- self.errmsg = "Expected " + self.name
- self.mayIndexError = False
- self.mayReturnEmpty = True
-
- def parseImpl(self, instring, loc, doActions=True):
- result = instring[loc] == self.firstQuoteChar and self.re_match(instring, loc) or None
- if not result:
- raise ParseException(instring, loc, self.errmsg, self)
-
- loc = result.end()
- ret = result.group()
-
- if self.unquoteResults:
-
- # strip off quotes
- ret = ret[self.quoteCharLen: -self.endQuoteCharLen]
-
- if isinstance(ret, basestring):
- # replace escaped whitespace
- if '\\' in ret and self.convertWhitespaceEscapes:
- ws_map = {
- r'\t': '\t',
- r'\n': '\n',
- r'\f': '\f',
- r'\r': '\r',
- }
- for wslit, wschar in ws_map.items():
- ret = ret.replace(wslit, wschar)
-
- # replace escaped characters
- if self.escChar:
- ret = re.sub(self.escCharReplacePattern, r"\g<1>", ret)
-
- # replace escaped quotes
- if self.escQuote:
- ret = ret.replace(self.escQuote, self.endQuoteChar)
-
- return loc, ret
-
- def __str__(self):
- try:
- return super(QuotedString, self).__str__()
- except Exception:
- pass
-
- if self.strRepr is None:
- self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar)
-
- return self.strRepr
-
-
-class CharsNotIn(Token):
- """Token for matching words composed of characters *not* in a given
- set (will include whitespace in matched characters if not listed in
- the provided exclusion set - see example). Defined with string
- containing all disallowed characters, and an optional minimum,
- maximum, and/or exact length. The default value for ``min`` is
- 1 (a minimum value < 1 is not valid); the default values for
- ``max`` and ``exact`` are 0, meaning no maximum or exact
- length restriction.
-
- Example::
-
- # define a comma-separated-value as anything that is not a ','
- csv_value = CharsNotIn(',')
- print(delimitedList(csv_value).parseString("dkls,lsdkjf,s12 34,@!#,213"))
-
- prints::
-
- ['dkls', 'lsdkjf', 's12 34', '@!#', '213']
- """
- def __init__(self, notChars, min=1, max=0, exact=0):
- super(CharsNotIn, self).__init__()
- self.skipWhitespace = False
- self.notChars = notChars
-
- if min < 1:
- raise ValueError("cannot specify a minimum length < 1; use "
- "Optional(CharsNotIn()) if zero-length char group is permitted")
-
- self.minLen = min
-
- if max > 0:
- self.maxLen = max
- else:
- self.maxLen = _MAX_INT
-
- if exact > 0:
- self.maxLen = exact
- self.minLen = exact
-
- self.name = _ustr(self)
- self.errmsg = "Expected " + self.name
- self.mayReturnEmpty = (self.minLen == 0)
- self.mayIndexError = False
-
- def parseImpl(self, instring, loc, doActions=True):
- if instring[loc] in self.notChars:
- raise ParseException(instring, loc, self.errmsg, self)
-
- start = loc
- loc += 1
- notchars = self.notChars
- maxlen = min(start + self.maxLen, len(instring))
- while loc < maxlen and instring[loc] not in notchars:
- loc += 1
-
- if loc - start < self.minLen:
- raise ParseException(instring, loc, self.errmsg, self)
-
- return loc, instring[start:loc]
-
- def __str__(self):
- try:
- return super(CharsNotIn, self).__str__()
- except Exception:
- pass
-
- if self.strRepr is None:
- if len(self.notChars) > 4:
- self.strRepr = "!W:(%s...)" % self.notChars[:4]
- else:
- self.strRepr = "!W:(%s)" % self.notChars
-
- return self.strRepr
-
-class White(Token):
- """Special matching class for matching whitespace. Normally,
- whitespace is ignored by pyparsing grammars. This class is included
- when some whitespace structures are significant. Define with
- a string containing the whitespace characters to be matched; default
- is ``" \\t\\r\\n"``. Also takes optional ``min``,
- ``max``, and ``exact`` arguments, as defined for the
- :class:`Word` class.
- """
- whiteStrs = {
- ' ' : '<SP>',
- '\t': '<TAB>',
- '\n': '<LF>',
- '\r': '<CR>',
- '\f': '<FF>',
- u'\u00A0': '<NBSP>',
- u'\u1680': '<OGHAM_SPACE_MARK>',
- u'\u180E': '<MONGOLIAN_VOWEL_SEPARATOR>',
- u'\u2000': '<EN_QUAD>',
- u'\u2001': '<EM_QUAD>',
- u'\u2002': '<EN_SPACE>',
- u'\u2003': '<EM_SPACE>',
- u'\u2004': '<THREE-PER-EM_SPACE>',
- u'\u2005': '<FOUR-PER-EM_SPACE>',
- u'\u2006': '<SIX-PER-EM_SPACE>',
- u'\u2007': '<FIGURE_SPACE>',
- u'\u2008': '<PUNCTUATION_SPACE>',
- u'\u2009': '<THIN_SPACE>',
- u'\u200A': '<HAIR_SPACE>',
- u'\u200B': '<ZERO_WIDTH_SPACE>',
- u'\u202F': '<NNBSP>',
- u'\u205F': '<MMSP>',
- u'\u3000': '<IDEOGRAPHIC_SPACE>',
- }
- def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0):
- super(White, self).__init__()
- self.matchWhite = ws
- self.setWhitespaceChars("".join(c for c in self.whiteChars if c not in self.matchWhite))
- # ~ self.leaveWhitespace()
- self.name = ("".join(White.whiteStrs[c] for c in self.matchWhite))
- self.mayReturnEmpty = True
- self.errmsg = "Expected " + self.name
-
- self.minLen = min
-
- if max > 0:
- self.maxLen = max
- else:
- self.maxLen = _MAX_INT
-
- if exact > 0:
- self.maxLen = exact
- self.minLen = exact
-
- def parseImpl(self, instring, loc, doActions=True):
- if instring[loc] not in self.matchWhite:
- raise ParseException(instring, loc, self.errmsg, self)
- start = loc
- loc += 1
- maxloc = start + self.maxLen
- maxloc = min(maxloc, len(instring))
- while loc < maxloc and instring[loc] in self.matchWhite:
- loc += 1
-
- if loc - start < self.minLen:
- raise ParseException(instring, loc, self.errmsg, self)
-
- return loc, instring[start:loc]
-
-
-class _PositionToken(Token):
- def __init__(self):
- super(_PositionToken, self).__init__()
- self.name = self.__class__.__name__
- self.mayReturnEmpty = True
- self.mayIndexError = False
-
-class GoToColumn(_PositionToken):
- """Token to advance to a specific column of input text; useful for
- tabular report scraping.
- """
- def __init__(self, colno):
- super(GoToColumn, self).__init__()
- self.col = colno
-
- def preParse(self, instring, loc):
- if col(loc, instring) != self.col:
- instrlen = len(instring)
- if self.ignoreExprs:
- loc = self._skipIgnorables(instring, loc)
- while loc < instrlen and instring[loc].isspace() and col(loc, instring) != self.col:
- loc += 1
- return loc
-
- def parseImpl(self, instring, loc, doActions=True):
- thiscol = col(loc, instring)
- if thiscol > self.col:
- raise ParseException(instring, loc, "Text not in expected column", self)
- newloc = loc + self.col - thiscol
- ret = instring[loc: newloc]
- return newloc, ret
-
-
-class LineStart(_PositionToken):
- r"""Matches if current position is at the beginning of a line within
- the parse string
-
- Example::
-
- test = '''\
- AAA this line
- AAA and this line
- AAA but not this one
- B AAA and definitely not this one
- '''
-
- for t in (LineStart() + 'AAA' + restOfLine).searchString(test):
- print(t)
-
- prints::
-
- ['AAA', ' this line']
- ['AAA', ' and this line']
-
- """
- def __init__(self):
- super(LineStart, self).__init__()
- self.errmsg = "Expected start of line"
-
- def parseImpl(self, instring, loc, doActions=True):
- if col(loc, instring) == 1:
- return loc, []
- raise ParseException(instring, loc, self.errmsg, self)
-
-class LineEnd(_PositionToken):
- """Matches if current position is at the end of a line within the
- parse string
- """
- def __init__(self):
- super(LineEnd, self).__init__()
- self.setWhitespaceChars(ParserElement.DEFAULT_WHITE_CHARS.replace("\n", ""))
- self.errmsg = "Expected end of line"
-
- def parseImpl(self, instring, loc, doActions=True):
- if loc < len(instring):
- if instring[loc] == "\n":
- return loc + 1, "\n"
- else:
- raise ParseException(instring, loc, self.errmsg, self)
- elif loc == len(instring):
- return loc + 1, []
- else:
- raise ParseException(instring, loc, self.errmsg, self)
-
-class StringStart(_PositionToken):
- """Matches if current position is at the beginning of the parse
- string
- """
- def __init__(self):
- super(StringStart, self).__init__()
- self.errmsg = "Expected start of text"
-
- def parseImpl(self, instring, loc, doActions=True):
- if loc != 0:
- # see if entire string up to here is just whitespace and ignoreables
- if loc != self.preParse(instring, 0):
- raise ParseException(instring, loc, self.errmsg, self)
- return loc, []
-
-class StringEnd(_PositionToken):
- """Matches if current position is at the end of the parse string
- """
- def __init__(self):
- super(StringEnd, self).__init__()
- self.errmsg = "Expected end of text"
-
- def parseImpl(self, instring, loc, doActions=True):
- if loc < len(instring):
- raise ParseException(instring, loc, self.errmsg, self)
- elif loc == len(instring):
- return loc + 1, []
- elif loc > len(instring):
- return loc, []
- else:
- raise ParseException(instring, loc, self.errmsg, self)
-
-class WordStart(_PositionToken):
- """Matches if the current position is at the beginning of a Word,
- and is not preceded by any character in a given set of
- ``wordChars`` (default= ``printables``). To emulate the
- ``\b`` behavior of regular expressions, use
- ``WordStart(alphanums)``. ``WordStart`` will also match at
- the beginning of the string being parsed, or at the beginning of
- a line.
- """
- def __init__(self, wordChars=printables):
- super(WordStart, self).__init__()
- self.wordChars = set(wordChars)
- self.errmsg = "Not at the start of a word"
-
- def parseImpl(self, instring, loc, doActions=True):
- if loc != 0:
- if (instring[loc - 1] in self.wordChars
- or instring[loc] not in self.wordChars):
- raise ParseException(instring, loc, self.errmsg, self)
- return loc, []
-
-class WordEnd(_PositionToken):
- """Matches if the current position is at the end of a Word, and is
- not followed by any character in a given set of ``wordChars``
- (default= ``printables``). To emulate the ``\b`` behavior of
- regular expressions, use ``WordEnd(alphanums)``. ``WordEnd``
- will also match at the end of the string being parsed, or at the end
- of a line.
- """
- def __init__(self, wordChars=printables):
- super(WordEnd, self).__init__()
- self.wordChars = set(wordChars)
- self.skipWhitespace = False
- self.errmsg = "Not at the end of a word"
-
- def parseImpl(self, instring, loc, doActions=True):
- instrlen = len(instring)
- if instrlen > 0 and loc < instrlen:
- if (instring[loc] in self.wordChars or
- instring[loc - 1] not in self.wordChars):
- raise ParseException(instring, loc, self.errmsg, self)
- return loc, []
-
-
-class ParseExpression(ParserElement):
- """Abstract subclass of ParserElement, for combining and
- post-processing parsed tokens.
- """
- def __init__(self, exprs, savelist=False):
- super(ParseExpression, self).__init__(savelist)
- if isinstance(exprs, _generatorType):
- exprs = list(exprs)
-
- if isinstance(exprs, basestring):
- self.exprs = [self._literalStringClass(exprs)]
- elif isinstance(exprs, ParserElement):
- self.exprs = [exprs]
- elif isinstance(exprs, Iterable):
- exprs = list(exprs)
- # if sequence of strings provided, wrap with Literal
- if any(isinstance(expr, basestring) for expr in exprs):
- exprs = (self._literalStringClass(e) if isinstance(e, basestring) else e for e in exprs)
- self.exprs = list(exprs)
- else:
- try:
- self.exprs = list(exprs)
- except TypeError:
- self.exprs = [exprs]
- self.callPreparse = False
-
- def append(self, other):
- self.exprs.append(other)
- self.strRepr = None
- return self
-
- def leaveWhitespace(self):
- """Extends ``leaveWhitespace`` defined in base class, and also invokes ``leaveWhitespace`` on
- all contained expressions."""
- self.skipWhitespace = False
- self.exprs = [e.copy() for e in self.exprs]
- for e in self.exprs:
- e.leaveWhitespace()
- return self
-
- def ignore(self, other):
- if isinstance(other, Suppress):
- if other not in self.ignoreExprs:
- super(ParseExpression, self).ignore(other)
- for e in self.exprs:
- e.ignore(self.ignoreExprs[-1])
- else:
- super(ParseExpression, self).ignore(other)
- for e in self.exprs:
- e.ignore(self.ignoreExprs[-1])
- return self
-
- def __str__(self):
- try:
- return super(ParseExpression, self).__str__()
- except Exception:
- pass
-
- if self.strRepr is None:
- self.strRepr = "%s:(%s)" % (self.__class__.__name__, _ustr(self.exprs))
- return self.strRepr
-
- def streamline(self):
- super(ParseExpression, self).streamline()
-
- for e in self.exprs:
- e.streamline()
-
- # collapse nested And's of the form And(And(And(a, b), c), d) to And(a, b, c, d)
- # but only if there are no parse actions or resultsNames on the nested And's
- # (likewise for Or's and MatchFirst's)
- if len(self.exprs) == 2:
- other = self.exprs[0]
- if (isinstance(other, self.__class__)
- and not other.parseAction
- and other.resultsName is None
- and not other.debug):
- self.exprs = other.exprs[:] + [self.exprs[1]]
- self.strRepr = None
- self.mayReturnEmpty |= other.mayReturnEmpty
- self.mayIndexError |= other.mayIndexError
-
- other = self.exprs[-1]
- if (isinstance(other, self.__class__)
- and not other.parseAction
- and other.resultsName is None
- and not other.debug):
- self.exprs = self.exprs[:-1] + other.exprs[:]
- self.strRepr = None
- self.mayReturnEmpty |= other.mayReturnEmpty
- self.mayIndexError |= other.mayIndexError
-
- self.errmsg = "Expected " + _ustr(self)
-
- return self
-
- def validate(self, validateTrace=None):
- tmp = (validateTrace if validateTrace is not None else [])[:] + [self]
- for e in self.exprs:
- e.validate(tmp)
- self.checkRecursion([])
-
- def copy(self):
- ret = super(ParseExpression, self).copy()
- ret.exprs = [e.copy() for e in self.exprs]
- return ret
-
- def _setResultsName(self, name, listAllMatches=False):
- if __diag__.warn_ungrouped_named_tokens_in_collection:
- for e in self.exprs:
- if isinstance(e, ParserElement) and e.resultsName:
- warnings.warn("{0}: setting results name {1!r} on {2} expression "
- "collides with {3!r} on contained expression".format("warn_ungrouped_named_tokens_in_collection",
- name,
- type(self).__name__,
- e.resultsName),
- stacklevel=3)
-
- return super(ParseExpression, self)._setResultsName(name, listAllMatches)
-
-
-class And(ParseExpression):
- """
- Requires all given :class:`ParseExpression` s to be found in the given order.
- Expressions may be separated by whitespace.
- May be constructed using the ``'+'`` operator.
- May also be constructed using the ``'-'`` operator, which will
- suppress backtracking.
-
- Example::
-
- integer = Word(nums)
- name_expr = OneOrMore(Word(alphas))
-
- expr = And([integer("id"), name_expr("name"), integer("age")])
- # more easily written as:
- expr = integer("id") + name_expr("name") + integer("age")
- """
-
- class _ErrorStop(Empty):
- def __init__(self, *args, **kwargs):
- super(And._ErrorStop, self).__init__(*args, **kwargs)
- self.name = '-'
- self.leaveWhitespace()
-
- def __init__(self, exprs, savelist=True):
- exprs = list(exprs)
- if exprs and Ellipsis in exprs:
- tmp = []
- for i, expr in enumerate(exprs):
- if expr is Ellipsis:
- if i < len(exprs) - 1:
- skipto_arg = (Empty() + exprs[i + 1]).exprs[-1]
- tmp.append(SkipTo(skipto_arg)("_skipped*"))
- else:
- raise Exception("cannot construct And with sequence ending in ...")
- else:
- tmp.append(expr)
- exprs[:] = tmp
- super(And, self).__init__(exprs, savelist)
- self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
- self.setWhitespaceChars(self.exprs[0].whiteChars)
- self.skipWhitespace = self.exprs[0].skipWhitespace
- self.callPreparse = True
-
- def streamline(self):
- # collapse any _PendingSkip's
- if self.exprs:
- if any(isinstance(e, ParseExpression) and e.exprs and isinstance(e.exprs[-1], _PendingSkip)
- for e in self.exprs[:-1]):
- for i, e in enumerate(self.exprs[:-1]):
- if e is None:
- continue
- if (isinstance(e, ParseExpression)
- and e.exprs and isinstance(e.exprs[-1], _PendingSkip)):
- e.exprs[-1] = e.exprs[-1] + self.exprs[i + 1]
- self.exprs[i + 1] = None
- self.exprs = [e for e in self.exprs if e is not None]
-
- super(And, self).streamline()
- self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
- return self
-
- def parseImpl(self, instring, loc, doActions=True):
- # pass False as last arg to _parse for first element, since we already
- # pre-parsed the string as part of our And pre-parsing
- loc, resultlist = self.exprs[0]._parse(instring, loc, doActions, callPreParse=False)
- errorStop = False
- for e in self.exprs[1:]:
- if isinstance(e, And._ErrorStop):
- errorStop = True
- continue
- if errorStop:
- try:
- loc, exprtokens = e._parse(instring, loc, doActions)
- except ParseSyntaxException:
- raise
- except ParseBaseException as pe:
- pe.__traceback__ = None
- raise ParseSyntaxException._from_exception(pe)
- except IndexError:
- raise ParseSyntaxException(instring, len(instring), self.errmsg, self)
- else:
- loc, exprtokens = e._parse(instring, loc, doActions)
- if exprtokens or exprtokens.haskeys():
- resultlist += exprtokens
- return loc, resultlist
-
- def __iadd__(self, other):
- if isinstance(other, basestring):
- other = self._literalStringClass(other)
- return self.append(other) # And([self, other])
-
- def checkRecursion(self, parseElementList):
- subRecCheckList = parseElementList[:] + [self]
- for e in self.exprs:
- e.checkRecursion(subRecCheckList)
- if not e.mayReturnEmpty:
- break
-
- def __str__(self):
- if hasattr(self, "name"):
- return self.name
-
- if self.strRepr is None:
- self.strRepr = "{" + " ".join(_ustr(e) for e in self.exprs) + "}"
-
- return self.strRepr
-
-
-class Or(ParseExpression):
- """Requires that at least one :class:`ParseExpression` is found. If
- two expressions match, the expression that matches the longest
- string will be used. May be constructed using the ``'^'``
- operator.
-
- Example::
-
- # construct Or using '^' operator
-
- number = Word(nums) ^ Combine(Word(nums) + '.' + Word(nums))
- print(number.searchString("123 3.1416 789"))
-
- prints::
-
- [['123'], ['3.1416'], ['789']]
- """
- def __init__(self, exprs, savelist=False):
- super(Or, self).__init__(exprs, savelist)
- if self.exprs:
- self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
- else:
- self.mayReturnEmpty = True
-
- def streamline(self):
- super(Or, self).streamline()
- if __compat__.collect_all_And_tokens:
- self.saveAsList = any(e.saveAsList for e in self.exprs)
- return self
-
- def parseImpl(self, instring, loc, doActions=True):
- maxExcLoc = -1
- maxException = None
- matches = []
- for e in self.exprs:
- try:
- loc2 = e.tryParse(instring, loc)
- except ParseException as err:
- err.__traceback__ = None
- if err.loc > maxExcLoc:
- maxException = err
- maxExcLoc = err.loc
- except IndexError:
- if len(instring) > maxExcLoc:
- maxException = ParseException(instring, len(instring), e.errmsg, self)
- maxExcLoc = len(instring)
- else:
- # save match among all matches, to retry longest to shortest
- matches.append((loc2, e))
-
- if matches:
- # re-evaluate all matches in descending order of length of match, in case attached actions
- # might change whether or how much they match of the input.
- matches.sort(key=itemgetter(0), reverse=True)
-
- if not doActions:
- # no further conditions or parse actions to change the selection of
- # alternative, so the first match will be the best match
- best_expr = matches[0][1]
- return best_expr._parse(instring, loc, doActions)
-
- longest = -1, None
- for loc1, expr1 in matches:
- if loc1 <= longest[0]:
- # already have a longer match than this one will deliver, we are done
- return longest
-
- try:
- loc2, toks = expr1._parse(instring, loc, doActions)
- except ParseException as err:
- err.__traceback__ = None
- if err.loc > maxExcLoc:
- maxException = err
- maxExcLoc = err.loc
- else:
- if loc2 >= loc1:
- return loc2, toks
- # didn't match as much as before
- elif loc2 > longest[0]:
- longest = loc2, toks
-
- if longest != (-1, None):
- return longest
-
- if maxException is not None:
- maxException.msg = self.errmsg
- raise maxException
- else:
- raise ParseException(instring, loc, "no defined alternatives to match", self)
-
-
- def __ixor__(self, other):
- if isinstance(other, basestring):
- other = self._literalStringClass(other)
- return self.append(other) # Or([self, other])
-
- def __str__(self):
- if hasattr(self, "name"):
- return self.name
-
- if self.strRepr is None:
- self.strRepr = "{" + " ^ ".join(_ustr(e) for e in self.exprs) + "}"
-
- return self.strRepr
-
- def checkRecursion(self, parseElementList):
- subRecCheckList = parseElementList[:] + [self]
- for e in self.exprs:
- e.checkRecursion(subRecCheckList)
-
- def _setResultsName(self, name, listAllMatches=False):
- if (not __compat__.collect_all_And_tokens
- and __diag__.warn_multiple_tokens_in_named_alternation):
- if any(isinstance(e, And) for e in self.exprs):
- warnings.warn("{0}: setting results name {1!r} on {2} expression "
- "may only return a single token for an And alternative, "
- "in future will return the full list of tokens".format(
- "warn_multiple_tokens_in_named_alternation", name, type(self).__name__),
- stacklevel=3)
-
- return super(Or, self)._setResultsName(name, listAllMatches)
-
-
-class MatchFirst(ParseExpression):
- """Requires that at least one :class:`ParseExpression` is found. If
- two expressions match, the first one listed is the one that will
- match. May be constructed using the ``'|'`` operator.
-
- Example::
-
- # construct MatchFirst using '|' operator
-
- # watch the order of expressions to match
- number = Word(nums) | Combine(Word(nums) + '.' + Word(nums))
- print(number.searchString("123 3.1416 789")) # Fail! -> [['123'], ['3'], ['1416'], ['789']]
-
- # put more selective expression first
- number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums)
- print(number.searchString("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']]
- """
- def __init__(self, exprs, savelist=False):
- super(MatchFirst, self).__init__(exprs, savelist)
- if self.exprs:
- self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
- else:
- self.mayReturnEmpty = True
-
- def streamline(self):
- super(MatchFirst, self).streamline()
- if __compat__.collect_all_And_tokens:
- self.saveAsList = any(e.saveAsList for e in self.exprs)
- return self
-
- def parseImpl(self, instring, loc, doActions=True):
- maxExcLoc = -1
- maxException = None
- for e in self.exprs:
- try:
- ret = e._parse(instring, loc, doActions)
- return ret
- except ParseException as err:
- if err.loc > maxExcLoc:
- maxException = err
- maxExcLoc = err.loc
- except IndexError:
- if len(instring) > maxExcLoc:
- maxException = ParseException(instring, len(instring), e.errmsg, self)
- maxExcLoc = len(instring)
-
- # only got here if no expression matched, raise exception for match that made it the furthest
- else:
- if maxException is not None:
- maxException.msg = self.errmsg
- raise maxException
- else:
- raise ParseException(instring, loc, "no defined alternatives to match", self)
-
- def __ior__(self, other):
- if isinstance(other, basestring):
- other = self._literalStringClass(other)
- return self.append(other) # MatchFirst([self, other])
-
- def __str__(self):
- if hasattr(self, "name"):
- return self.name
-
- if self.strRepr is None:
- self.strRepr = "{" + " | ".join(_ustr(e) for e in self.exprs) + "}"
-
- return self.strRepr
-
- def checkRecursion(self, parseElementList):
- subRecCheckList = parseElementList[:] + [self]
- for e in self.exprs:
- e.checkRecursion(subRecCheckList)
-
- def _setResultsName(self, name, listAllMatches=False):
- if (not __compat__.collect_all_And_tokens
- and __diag__.warn_multiple_tokens_in_named_alternation):
- if any(isinstance(e, And) for e in self.exprs):
- warnings.warn("{0}: setting results name {1!r} on {2} expression "
- "may only return a single token for an And alternative, "
- "in future will return the full list of tokens".format(
- "warn_multiple_tokens_in_named_alternation", name, type(self).__name__),
- stacklevel=3)
-
- return super(MatchFirst, self)._setResultsName(name, listAllMatches)
-
-
-class Each(ParseExpression):
- """Requires all given :class:`ParseExpression` s to be found, but in
- any order. Expressions may be separated by whitespace.
-
- May be constructed using the ``'&'`` operator.
-
- Example::
-
- color = oneOf("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN")
- shape_type = oneOf("SQUARE CIRCLE TRIANGLE STAR HEXAGON OCTAGON")
- integer = Word(nums)
- shape_attr = "shape:" + shape_type("shape")
- posn_attr = "posn:" + Group(integer("x") + ',' + integer("y"))("posn")
- color_attr = "color:" + color("color")
- size_attr = "size:" + integer("size")
-
- # use Each (using operator '&') to accept attributes in any order
- # (shape and posn are required, color and size are optional)
- shape_spec = shape_attr & posn_attr & Optional(color_attr) & Optional(size_attr)
-
- shape_spec.runTests('''
- shape: SQUARE color: BLACK posn: 100, 120
- shape: CIRCLE size: 50 color: BLUE posn: 50,80
- color:GREEN size:20 shape:TRIANGLE posn:20,40
- '''
- )
-
- prints::
-
- shape: SQUARE color: BLACK posn: 100, 120
- ['shape:', 'SQUARE', 'color:', 'BLACK', 'posn:', ['100', ',', '120']]
- - color: BLACK
- - posn: ['100', ',', '120']
- - x: 100
- - y: 120
- - shape: SQUARE
-
-
- shape: CIRCLE size: 50 color: BLUE posn: 50,80
- ['shape:', 'CIRCLE', 'size:', '50', 'color:', 'BLUE', 'posn:', ['50', ',', '80']]
- - color: BLUE
- - posn: ['50', ',', '80']
- - x: 50
- - y: 80
- - shape: CIRCLE
- - size: 50
-
-
- color: GREEN size: 20 shape: TRIANGLE posn: 20,40
- ['color:', 'GREEN', 'size:', '20', 'shape:', 'TRIANGLE', 'posn:', ['20', ',', '40']]
- - color: GREEN
- - posn: ['20', ',', '40']
- - x: 20
- - y: 40
- - shape: TRIANGLE
- - size: 20
- """
- def __init__(self, exprs, savelist=True):
- super(Each, self).__init__(exprs, savelist)
- self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
- self.skipWhitespace = True
- self.initExprGroups = True
- self.saveAsList = True
-
- def streamline(self):
- super(Each, self).streamline()
- self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
- return self
-
- def parseImpl(self, instring, loc, doActions=True):
- if self.initExprGroups:
- self.opt1map = dict((id(e.expr), e) for e in self.exprs if isinstance(e, Optional))
- opt1 = [e.expr for e in self.exprs if isinstance(e, Optional)]
- opt2 = [e for e in self.exprs if e.mayReturnEmpty and not isinstance(e, (Optional, Regex))]
- self.optionals = opt1 + opt2
- self.multioptionals = [e.expr for e in self.exprs if isinstance(e, ZeroOrMore)]
- self.multirequired = [e.expr for e in self.exprs if isinstance(e, OneOrMore)]
- self.required = [e for e in self.exprs if not isinstance(e, (Optional, ZeroOrMore, OneOrMore))]
- self.required += self.multirequired
- self.initExprGroups = False
- tmpLoc = loc
- tmpReqd = self.required[:]
- tmpOpt = self.optionals[:]
- matchOrder = []
-
- keepMatching = True
- while keepMatching:
- tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired
- failed = []
- for e in tmpExprs:
- try:
- tmpLoc = e.tryParse(instring, tmpLoc)
- except ParseException:
- failed.append(e)
- else:
- matchOrder.append(self.opt1map.get(id(e), e))
- if e in tmpReqd:
- tmpReqd.remove(e)
- elif e in tmpOpt:
- tmpOpt.remove(e)
- if len(failed) == len(tmpExprs):
- keepMatching = False
-
- if tmpReqd:
- missing = ", ".join(_ustr(e) for e in tmpReqd)
- raise ParseException(instring, loc, "Missing one or more required elements (%s)" % missing)
-
- # add any unmatched Optionals, in case they have default values defined
- matchOrder += [e for e in self.exprs if isinstance(e, Optional) and e.expr in tmpOpt]
-
- resultlist = []
- for e in matchOrder:
- loc, results = e._parse(instring, loc, doActions)
- resultlist.append(results)
-
- finalResults = sum(resultlist, ParseResults([]))
- return loc, finalResults
-
- def __str__(self):
- if hasattr(self, "name"):
- return self.name
-
- if self.strRepr is None:
- self.strRepr = "{" + " & ".join(_ustr(e) for e in self.exprs) + "}"
-
- return self.strRepr
-
- def checkRecursion(self, parseElementList):
- subRecCheckList = parseElementList[:] + [self]
- for e in self.exprs:
- e.checkRecursion(subRecCheckList)
-
-
-class ParseElementEnhance(ParserElement):
- """Abstract subclass of :class:`ParserElement`, for combining and
- post-processing parsed tokens.
- """
- def __init__(self, expr, savelist=False):
- super(ParseElementEnhance, self).__init__(savelist)
- if isinstance(expr, basestring):
- if issubclass(self._literalStringClass, Token):
- expr = self._literalStringClass(expr)
- else:
- expr = self._literalStringClass(Literal(expr))
- self.expr = expr
- self.strRepr = None
- if expr is not None:
- self.mayIndexError = expr.mayIndexError
- self.mayReturnEmpty = expr.mayReturnEmpty
- self.setWhitespaceChars(expr.whiteChars)
- self.skipWhitespace = expr.skipWhitespace
- self.saveAsList = expr.saveAsList
- self.callPreparse = expr.callPreparse
- self.ignoreExprs.extend(expr.ignoreExprs)
-
- def parseImpl(self, instring, loc, doActions=True):
- if self.expr is not None:
- return self.expr._parse(instring, loc, doActions, callPreParse=False)
- else:
- raise ParseException("", loc, self.errmsg, self)
-
- def leaveWhitespace(self):
- self.skipWhitespace = False
- self.expr = self.expr.copy()
- if self.expr is not None:
- self.expr.leaveWhitespace()
- return self
-
- def ignore(self, other):
- if isinstance(other, Suppress):
- if other not in self.ignoreExprs:
- super(ParseElementEnhance, self).ignore(other)
- if self.expr is not None:
- self.expr.ignore(self.ignoreExprs[-1])
- else:
- super(ParseElementEnhance, self).ignore(other)
- if self.expr is not None:
- self.expr.ignore(self.ignoreExprs[-1])
- return self
-
- def streamline(self):
- super(ParseElementEnhance, self).streamline()
- if self.expr is not None:
- self.expr.streamline()
- return self
-
- def checkRecursion(self, parseElementList):
- if self in parseElementList:
- raise RecursiveGrammarException(parseElementList + [self])
- subRecCheckList = parseElementList[:] + [self]
- if self.expr is not None:
- self.expr.checkRecursion(subRecCheckList)
-
- def validate(self, validateTrace=None):
- if validateTrace is None:
- validateTrace = []
- tmp = validateTrace[:] + [self]
- if self.expr is not None:
- self.expr.validate(tmp)
- self.checkRecursion([])
-
- def __str__(self):
- try:
- return super(ParseElementEnhance, self).__str__()
- except Exception:
- pass
-
- if self.strRepr is None and self.expr is not None:
- self.strRepr = "%s:(%s)" % (self.__class__.__name__, _ustr(self.expr))
- return self.strRepr
-
-
-class FollowedBy(ParseElementEnhance):
- """Lookahead matching of the given parse expression.
- ``FollowedBy`` does *not* advance the parsing position within
- the input string, it only verifies that the specified parse
- expression matches at the current position. ``FollowedBy``
- always returns a null token list. If any results names are defined
- in the lookahead expression, those *will* be returned for access by
- name.
-
- Example::
-
- # use FollowedBy to match a label only if it is followed by a ':'
- data_word = Word(alphas)
- label = data_word + FollowedBy(':')
- attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
-
- OneOrMore(attr_expr).parseString("shape: SQUARE color: BLACK posn: upper left").pprint()
-
- prints::
-
- [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']]
- """
- def __init__(self, expr):
- super(FollowedBy, self).__init__(expr)
- self.mayReturnEmpty = True
-
- def parseImpl(self, instring, loc, doActions=True):
- # by using self._expr.parse and deleting the contents of the returned ParseResults list
- # we keep any named results that were defined in the FollowedBy expression
- _, ret = self.expr._parse(instring, loc, doActions=doActions)
- del ret[:]
-
- return loc, ret
-
-
-class PrecededBy(ParseElementEnhance):
- """Lookbehind matching of the given parse expression.
- ``PrecededBy`` does not advance the parsing position within the
- input string, it only verifies that the specified parse expression
- matches prior to the current position. ``PrecededBy`` always
- returns a null token list, but if a results name is defined on the
- given expression, it is returned.
-
- Parameters:
-
- - expr - expression that must match prior to the current parse
- location
- - retreat - (default= ``None``) - (int) maximum number of characters
- to lookbehind prior to the current parse location
-
- If the lookbehind expression is a string, Literal, Keyword, or
- a Word or CharsNotIn with a specified exact or maximum length, then
- the retreat parameter is not required. Otherwise, retreat must be
- specified to give a maximum number of characters to look back from
- the current parse position for a lookbehind match.
-
- Example::
-
- # VB-style variable names with type prefixes
- int_var = PrecededBy("#") + pyparsing_common.identifier
- str_var = PrecededBy("$") + pyparsing_common.identifier
-
- """
- def __init__(self, expr, retreat=None):
- super(PrecededBy, self).__init__(expr)
- self.expr = self.expr().leaveWhitespace()
- self.mayReturnEmpty = True
- self.mayIndexError = False
- self.exact = False
- if isinstance(expr, str):
- retreat = len(expr)
- self.exact = True
- elif isinstance(expr, (Literal, Keyword)):
- retreat = expr.matchLen
- self.exact = True
- elif isinstance(expr, (Word, CharsNotIn)) and expr.maxLen != _MAX_INT:
- retreat = expr.maxLen
- self.exact = True
- elif isinstance(expr, _PositionToken):
- retreat = 0
- self.exact = True
- self.retreat = retreat
- self.errmsg = "not preceded by " + str(expr)
- self.skipWhitespace = False
- self.parseAction.append(lambda s, l, t: t.__delitem__(slice(None, None)))
-
- def parseImpl(self, instring, loc=0, doActions=True):
- if self.exact:
- if loc < self.retreat:
- raise ParseException(instring, loc, self.errmsg)
- start = loc - self.retreat
- _, ret = self.expr._parse(instring, start)
- else:
- # retreat specified a maximum lookbehind window, iterate
- test_expr = self.expr + StringEnd()
- instring_slice = instring[max(0, loc - self.retreat):loc]
- last_expr = ParseException(instring, loc, self.errmsg)
- for offset in range(1, min(loc, self.retreat + 1)+1):
- try:
- # print('trying', offset, instring_slice, repr(instring_slice[loc - offset:]))
- _, ret = test_expr._parse(instring_slice, len(instring_slice) - offset)
- except ParseBaseException as pbe:
- last_expr = pbe
- else:
- break
- else:
- raise last_expr
- return loc, ret
-
-
-class NotAny(ParseElementEnhance):
- """Lookahead to disallow matching with the given parse expression.
- ``NotAny`` does *not* advance the parsing position within the
- input string, it only verifies that the specified parse expression
- does *not* match at the current position. Also, ``NotAny`` does
- *not* skip over leading whitespace. ``NotAny`` always returns
- a null token list. May be constructed using the '~' operator.
-
- Example::
-
- AND, OR, NOT = map(CaselessKeyword, "AND OR NOT".split())
-
- # take care not to mistake keywords for identifiers
- ident = ~(AND | OR | NOT) + Word(alphas)
- boolean_term = Optional(NOT) + ident
-
- # very crude boolean expression - to support parenthesis groups and
- # operation hierarchy, use infixNotation
- boolean_expr = boolean_term + ZeroOrMore((AND | OR) + boolean_term)
-
- # integers that are followed by "." are actually floats
- integer = Word(nums) + ~Char(".")
- """
- def __init__(self, expr):
- super(NotAny, self).__init__(expr)
- # ~ self.leaveWhitespace()
- self.skipWhitespace = False # do NOT use self.leaveWhitespace(), don't want to propagate to exprs
- self.mayReturnEmpty = True
- self.errmsg = "Found unwanted token, " + _ustr(self.expr)
-
- def parseImpl(self, instring, loc, doActions=True):
- if self.expr.canParseNext(instring, loc):
- raise ParseException(instring, loc, self.errmsg, self)
- return loc, []
-
- def __str__(self):
- if hasattr(self, "name"):
- return self.name
-
- if self.strRepr is None:
- self.strRepr = "~{" + _ustr(self.expr) + "}"
-
- return self.strRepr
-
-class _MultipleMatch(ParseElementEnhance):
- def __init__(self, expr, stopOn=None):
- super(_MultipleMatch, self).__init__(expr)
- self.saveAsList = True
- ender = stopOn
- if isinstance(ender, basestring):
- ender = self._literalStringClass(ender)
- self.stopOn(ender)
-
- def stopOn(self, ender):
- if isinstance(ender, basestring):
- ender = self._literalStringClass(ender)
- self.not_ender = ~ender if ender is not None else None
- return self
-
- def parseImpl(self, instring, loc, doActions=True):
- self_expr_parse = self.expr._parse
- self_skip_ignorables = self._skipIgnorables
- check_ender = self.not_ender is not None
- if check_ender:
- try_not_ender = self.not_ender.tryParse
-
- # must be at least one (but first see if we are the stopOn sentinel;
- # if so, fail)
- if check_ender:
- try_not_ender(instring, loc)
- loc, tokens = self_expr_parse(instring, loc, doActions, callPreParse=False)
- try:
- hasIgnoreExprs = (not not self.ignoreExprs)
- while 1:
- if check_ender:
- try_not_ender(instring, loc)
- if hasIgnoreExprs:
- preloc = self_skip_ignorables(instring, loc)
- else:
- preloc = loc
- loc, tmptokens = self_expr_parse(instring, preloc, doActions)
- if tmptokens or tmptokens.haskeys():
- tokens += tmptokens
- except (ParseException, IndexError):
- pass
-
- return loc, tokens
-
- def _setResultsName(self, name, listAllMatches=False):
- if __diag__.warn_ungrouped_named_tokens_in_collection:
- for e in [self.expr] + getattr(self.expr, 'exprs', []):
- if isinstance(e, ParserElement) and e.resultsName:
- warnings.warn("{0}: setting results name {1!r} on {2} expression "
- "collides with {3!r} on contained expression".format("warn_ungrouped_named_tokens_in_collection",
- name,
- type(self).__name__,
- e.resultsName),
- stacklevel=3)
-
- return super(_MultipleMatch, self)._setResultsName(name, listAllMatches)
-
-
-class OneOrMore(_MultipleMatch):
- """Repetition of one or more of the given expression.
-
- Parameters:
- - expr - expression that must match one or more times
- - stopOn - (default= ``None``) - expression for a terminating sentinel
- (only required if the sentinel would ordinarily match the repetition
- expression)
-
- Example::
-
- data_word = Word(alphas)
- label = data_word + FollowedBy(':')
- attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
-
- text = "shape: SQUARE posn: upper left color: BLACK"
- OneOrMore(attr_expr).parseString(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
-
- # use stopOn attribute for OneOrMore to avoid reading label string as part of the data
- attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
- OneOrMore(attr_expr).parseString(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']]
-
- # could also be written as
- (attr_expr * (1,)).parseString(text).pprint()
- """
-
- def __str__(self):
- if hasattr(self, "name"):
- return self.name
-
- if self.strRepr is None:
- self.strRepr = "{" + _ustr(self.expr) + "}..."
-
- return self.strRepr
-
-class ZeroOrMore(_MultipleMatch):
- """Optional repetition of zero or more of the given expression.
-
- Parameters:
- - expr - expression that must match zero or more times
- - stopOn - (default= ``None``) - expression for a terminating sentinel
- (only required if the sentinel would ordinarily match the repetition
- expression)
-
- Example: similar to :class:`OneOrMore`
- """
- def __init__(self, expr, stopOn=None):
- super(ZeroOrMore, self).__init__(expr, stopOn=stopOn)
- self.mayReturnEmpty = True
-
- def parseImpl(self, instring, loc, doActions=True):
- try:
- return super(ZeroOrMore, self).parseImpl(instring, loc, doActions)
- except (ParseException, IndexError):
- return loc, []
-
- def __str__(self):
- if hasattr(self, "name"):
- return self.name
-
- if self.strRepr is None:
- self.strRepr = "[" + _ustr(self.expr) + "]..."
-
- return self.strRepr
-
-
-class _NullToken(object):
- def __bool__(self):
- return False
- __nonzero__ = __bool__
- def __str__(self):
- return ""
-
-class Optional(ParseElementEnhance):
- """Optional matching of the given expression.
-
- Parameters:
- - expr - expression that must match zero or more times
- - default (optional) - value to be returned if the optional expression is not found.
-
- Example::
-
- # US postal code can be a 5-digit zip, plus optional 4-digit qualifier
- zip = Combine(Word(nums, exact=5) + Optional('-' + Word(nums, exact=4)))
- zip.runTests('''
- # traditional ZIP code
- 12345
-
- # ZIP+4 form
- 12101-0001
-
- # invalid ZIP
- 98765-
- ''')
-
- prints::
-
- # traditional ZIP code
- 12345
- ['12345']
-
- # ZIP+4 form
- 12101-0001
- ['12101-0001']
-
- # invalid ZIP
- 98765-
- ^
- FAIL: Expected end of text (at char 5), (line:1, col:6)
- """
- __optionalNotMatched = _NullToken()
-
- def __init__(self, expr, default=__optionalNotMatched):
- super(Optional, self).__init__(expr, savelist=False)
- self.saveAsList = self.expr.saveAsList
- self.defaultValue = default
- self.mayReturnEmpty = True
-
- def parseImpl(self, instring, loc, doActions=True):
- try:
- loc, tokens = self.expr._parse(instring, loc, doActions, callPreParse=False)
- except (ParseException, IndexError):
- if self.defaultValue is not self.__optionalNotMatched:
- if self.expr.resultsName:
- tokens = ParseResults([self.defaultValue])
- tokens[self.expr.resultsName] = self.defaultValue
- else:
- tokens = [self.defaultValue]
- else:
- tokens = []
- return loc, tokens
-
- def __str__(self):
- if hasattr(self, "name"):
- return self.name
-
- if self.strRepr is None:
- self.strRepr = "[" + _ustr(self.expr) + "]"
-
- return self.strRepr
-
-class SkipTo(ParseElementEnhance):
- """Token for skipping over all undefined text until the matched
- expression is found.
-
- Parameters:
- - expr - target expression marking the end of the data to be skipped
- - include - (default= ``False``) if True, the target expression is also parsed
- (the skipped text and target expression are returned as a 2-element list).
- - ignore - (default= ``None``) used to define grammars (typically quoted strings and
- comments) that might contain false matches to the target expression
- - failOn - (default= ``None``) define expressions that are not allowed to be
- included in the skipped test; if found before the target expression is found,
- the SkipTo is not a match
-
- Example::
-
- report = '''
- Outstanding Issues Report - 1 Jan 2000
-
- # | Severity | Description | Days Open
- -----+----------+-------------------------------------------+-----------
- 101 | Critical | Intermittent system crash | 6
- 94 | Cosmetic | Spelling error on Login ('log|n') | 14
- 79 | Minor | System slow when running too many reports | 47
- '''
- integer = Word(nums)
- SEP = Suppress('|')
- # use SkipTo to simply match everything up until the next SEP
- # - ignore quoted strings, so that a '|' character inside a quoted string does not match
- # - parse action will call token.strip() for each matched token, i.e., the description body
- string_data = SkipTo(SEP, ignore=quotedString)
- string_data.setParseAction(tokenMap(str.strip))
- ticket_expr = (integer("issue_num") + SEP
- + string_data("sev") + SEP
- + string_data("desc") + SEP
- + integer("days_open"))
-
- for tkt in ticket_expr.searchString(report):
- print tkt.dump()
-
- prints::
-
- ['101', 'Critical', 'Intermittent system crash', '6']
- - days_open: 6
- - desc: Intermittent system crash
- - issue_num: 101
- - sev: Critical
- ['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14']
- - days_open: 14
- - desc: Spelling error on Login ('log|n')
- - issue_num: 94
- - sev: Cosmetic
- ['79', 'Minor', 'System slow when running too many reports', '47']
- - days_open: 47
- - desc: System slow when running too many reports
- - issue_num: 79
- - sev: Minor
- """
- def __init__(self, other, include=False, ignore=None, failOn=None):
- super(SkipTo, self).__init__(other)
- self.ignoreExpr = ignore
- self.mayReturnEmpty = True
- self.mayIndexError = False
- self.includeMatch = include
- self.saveAsList = False
- if isinstance(failOn, basestring):
- self.failOn = self._literalStringClass(failOn)
- else:
- self.failOn = failOn
- self.errmsg = "No match found for " + _ustr(self.expr)
-
- def parseImpl(self, instring, loc, doActions=True):
- startloc = loc
- instrlen = len(instring)
- expr = self.expr
- expr_parse = self.expr._parse
- self_failOn_canParseNext = self.failOn.canParseNext if self.failOn is not None else None
- self_ignoreExpr_tryParse = self.ignoreExpr.tryParse if self.ignoreExpr is not None else None
-
- tmploc = loc
- while tmploc <= instrlen:
- if self_failOn_canParseNext is not None:
- # break if failOn expression matches
- if self_failOn_canParseNext(instring, tmploc):
- break
-
- if self_ignoreExpr_tryParse is not None:
- # advance past ignore expressions
- while 1:
- try:
- tmploc = self_ignoreExpr_tryParse(instring, tmploc)
- except ParseBaseException:
- break
-
- try:
- expr_parse(instring, tmploc, doActions=False, callPreParse=False)
- except (ParseException, IndexError):
- # no match, advance loc in string
- tmploc += 1
- else:
- # matched skipto expr, done
- break
-
- else:
- # ran off the end of the input string without matching skipto expr, fail
- raise ParseException(instring, loc, self.errmsg, self)
-
- # build up return values
- loc = tmploc
- skiptext = instring[startloc:loc]
- skipresult = ParseResults(skiptext)
-
- if self.includeMatch:
- loc, mat = expr_parse(instring, loc, doActions, callPreParse=False)
- skipresult += mat
-
- return loc, skipresult
-
-class Forward(ParseElementEnhance):
- """Forward declaration of an expression to be defined later -
- used for recursive grammars, such as algebraic infix notation.
- When the expression is known, it is assigned to the ``Forward``
- variable using the '<<' operator.
-
- Note: take care when assigning to ``Forward`` not to overlook
- precedence of operators.
-
- Specifically, '|' has a lower precedence than '<<', so that::
-
- fwdExpr << a | b | c
-
- will actually be evaluated as::
-
- (fwdExpr << a) | b | c
-
- thereby leaving b and c out as parseable alternatives. It is recommended that you
- explicitly group the values inserted into the ``Forward``::
-
- fwdExpr << (a | b | c)
-
- Converting to use the '<<=' operator instead will avoid this problem.
-
- See :class:`ParseResults.pprint` for an example of a recursive
- parser created using ``Forward``.
- """
- def __init__(self, other=None):
- super(Forward, self).__init__(other, savelist=False)
-
- def __lshift__(self, other):
- if isinstance(other, basestring):
- other = self._literalStringClass(other)
- self.expr = other
- self.strRepr = None
- self.mayIndexError = self.expr.mayIndexError
- self.mayReturnEmpty = self.expr.mayReturnEmpty
- self.setWhitespaceChars(self.expr.whiteChars)
- self.skipWhitespace = self.expr.skipWhitespace
- self.saveAsList = self.expr.saveAsList
- self.ignoreExprs.extend(self.expr.ignoreExprs)
- return self
-
- def __ilshift__(self, other):
- return self << other
-
- def leaveWhitespace(self):
- self.skipWhitespace = False
- return self
-
- def streamline(self):
- if not self.streamlined:
- self.streamlined = True
- if self.expr is not None:
- self.expr.streamline()
- return self
-
- def validate(self, validateTrace=None):
- if validateTrace is None:
- validateTrace = []
-
- if self not in validateTrace:
- tmp = validateTrace[:] + [self]
- if self.expr is not None:
- self.expr.validate(tmp)
- self.checkRecursion([])
-
- def __str__(self):
- if hasattr(self, "name"):
- return self.name
- if self.strRepr is not None:
- return self.strRepr
-
- # Avoid infinite recursion by setting a temporary strRepr
- self.strRepr = ": ..."
-
- # Use the string representation of main expression.
- retString = '...'
- try:
- if self.expr is not None:
- retString = _ustr(self.expr)[:1000]
- else:
- retString = "None"
- finally:
- self.strRepr = self.__class__.__name__ + ": " + retString
- return self.strRepr
-
- def copy(self):
- if self.expr is not None:
- return super(Forward, self).copy()
- else:
- ret = Forward()
- ret <<= self
- return ret
-
- def _setResultsName(self, name, listAllMatches=False):
- if __diag__.warn_name_set_on_empty_Forward:
- if self.expr is None:
- warnings.warn("{0}: setting results name {0!r} on {1} expression "
- "that has no contained expression".format("warn_name_set_on_empty_Forward",
- name,
- type(self).__name__),
- stacklevel=3)
-
- return super(Forward, self)._setResultsName(name, listAllMatches)
-
-class TokenConverter(ParseElementEnhance):
- """
- Abstract subclass of :class:`ParseExpression`, for converting parsed results.
- """
- def __init__(self, expr, savelist=False):
- super(TokenConverter, self).__init__(expr) # , savelist)
- self.saveAsList = False
-
-class Combine(TokenConverter):
- """Converter to concatenate all matching tokens to a single string.
- By default, the matching patterns must also be contiguous in the
- input string; this can be disabled by specifying
- ``'adjacent=False'`` in the constructor.
-
- Example::
-
- real = Word(nums) + '.' + Word(nums)
- print(real.parseString('3.1416')) # -> ['3', '.', '1416']
- # will also erroneously match the following
- print(real.parseString('3. 1416')) # -> ['3', '.', '1416']
-
- real = Combine(Word(nums) + '.' + Word(nums))
- print(real.parseString('3.1416')) # -> ['3.1416']
- # no match when there are internal spaces
- print(real.parseString('3. 1416')) # -> Exception: Expected W:(0123...)
- """
- def __init__(self, expr, joinString="", adjacent=True):
- super(Combine, self).__init__(expr)
- # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself
- if adjacent:
- self.leaveWhitespace()
- self.adjacent = adjacent
- self.skipWhitespace = True
- self.joinString = joinString
- self.callPreparse = True
-
- def ignore(self, other):
- if self.adjacent:
- ParserElement.ignore(self, other)
- else:
- super(Combine, self).ignore(other)
- return self
-
- def postParse(self, instring, loc, tokenlist):
- retToks = tokenlist.copy()
- del retToks[:]
- retToks += ParseResults(["".join(tokenlist._asStringList(self.joinString))], modal=self.modalResults)
-
- if self.resultsName and retToks.haskeys():
- return [retToks]
- else:
- return retToks
-
-class Group(TokenConverter):
- """Converter to return the matched tokens as a list - useful for
- returning tokens of :class:`ZeroOrMore` and :class:`OneOrMore` expressions.
-
- Example::
-
- ident = Word(alphas)
- num = Word(nums)
- term = ident | num
- func = ident + Optional(delimitedList(term))
- print(func.parseString("fn a, b, 100")) # -> ['fn', 'a', 'b', '100']
-
- func = ident + Group(Optional(delimitedList(term)))
- print(func.parseString("fn a, b, 100")) # -> ['fn', ['a', 'b', '100']]
- """
- def __init__(self, expr):
- super(Group, self).__init__(expr)
- self.saveAsList = True
-
- def postParse(self, instring, loc, tokenlist):
- return [tokenlist]
-
-class Dict(TokenConverter):
- """Converter to return a repetitive expression as a list, but also
- as a dictionary. Each element can also be referenced using the first
- token in the expression as its key. Useful for tabular report
- scraping when the first column can be used as a item key.
-
- Example::
-
- data_word = Word(alphas)
- label = data_word + FollowedBy(':')
- attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
-
- text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
- attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
-
- # print attributes as plain groups
- print(OneOrMore(attr_expr).parseString(text).dump())
-
- # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names
- result = Dict(OneOrMore(Group(attr_expr))).parseString(text)
- print(result.dump())
-
- # access named fields as dict entries, or output as dict
- print(result['shape'])
- print(result.asDict())
-
- prints::
-
- ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap']
- [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
- - color: light blue
- - posn: upper left
- - shape: SQUARE
- - texture: burlap
- SQUARE
- {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'}
-
- See more examples at :class:`ParseResults` of accessing fields by results name.
- """
- def __init__(self, expr):
- super(Dict, self).__init__(expr)
- self.saveAsList = True
-
- def postParse(self, instring, loc, tokenlist):
- for i, tok in enumerate(tokenlist):
- if len(tok) == 0:
- continue
- ikey = tok[0]
- if isinstance(ikey, int):
- ikey = _ustr(tok[0]).strip()
- if len(tok) == 1:
- tokenlist[ikey] = _ParseResultsWithOffset("", i)
- elif len(tok) == 2 and not isinstance(tok[1], ParseResults):
- tokenlist[ikey] = _ParseResultsWithOffset(tok[1], i)
- else:
- dictvalue = tok.copy() # ParseResults(i)
- del dictvalue[0]
- if len(dictvalue) != 1 or (isinstance(dictvalue, ParseResults) and dictvalue.haskeys()):
- tokenlist[ikey] = _ParseResultsWithOffset(dictvalue, i)
- else:
- tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0], i)
-
- if self.resultsName:
- return [tokenlist]
- else:
- return tokenlist
-
-
-class Suppress(TokenConverter):
- """Converter for ignoring the results of a parsed expression.
-
- Example::
-
- source = "a, b, c,d"
- wd = Word(alphas)
- wd_list1 = wd + ZeroOrMore(',' + wd)
- print(wd_list1.parseString(source))
-
- # often, delimiters that are useful during parsing are just in the
- # way afterward - use Suppress to keep them out of the parsed output
- wd_list2 = wd + ZeroOrMore(Suppress(',') + wd)
- print(wd_list2.parseString(source))
-
- prints::
-
- ['a', ',', 'b', ',', 'c', ',', 'd']
- ['a', 'b', 'c', 'd']
-
- (See also :class:`delimitedList`.)
- """
- def postParse(self, instring, loc, tokenlist):
- return []
-
- def suppress(self):
- return self
-
-
-class OnlyOnce(object):
- """Wrapper for parse actions, to ensure they are only called once.
- """
- def __init__(self, methodCall):
- self.callable = _trim_arity(methodCall)
- self.called = False
- def __call__(self, s, l, t):
- if not self.called:
- results = self.callable(s, l, t)
- self.called = True
- return results
- raise ParseException(s, l, "")
- def reset(self):
- self.called = False
-
-def traceParseAction(f):
- """Decorator for debugging parse actions.
-
- When the parse action is called, this decorator will print
- ``">> entering method-name(line:<current_source_line>, <parse_location>, <matched_tokens>)"``.
- When the parse action completes, the decorator will print
- ``"<<"`` followed by the returned value, or any exception that the parse action raised.
-
- Example::
-
- wd = Word(alphas)
-
- @traceParseAction
- def remove_duplicate_chars(tokens):
- return ''.join(sorted(set(''.join(tokens))))
-
- wds = OneOrMore(wd).setParseAction(remove_duplicate_chars)
- print(wds.parseString("slkdjs sld sldd sdlf sdljf"))
-
- prints::
-
- >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {}))
- <<leaving remove_duplicate_chars (ret: 'dfjkls')
- ['dfjkls']
- """
- f = _trim_arity(f)
- def z(*paArgs):
- thisFunc = f.__name__
- s, l, t = paArgs[-3:]
- if len(paArgs) > 3:
- thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc
- sys.stderr.write(">>entering %s(line: '%s', %d, %r)\n" % (thisFunc, line(l, s), l, t))
- try:
- ret = f(*paArgs)
- except Exception as exc:
- sys.stderr.write("<<leaving %s (exception: %s)\n" % (thisFunc, exc))
- raise
- sys.stderr.write("<<leaving %s (ret: %r)\n" % (thisFunc, ret))
- return ret
- try:
- z.__name__ = f.__name__
- except AttributeError:
- pass
- return z
-
-#
-# global helpers
-#
-def delimitedList(expr, delim=",", combine=False):
- """Helper to define a delimited list of expressions - the delimiter
- defaults to ','. By default, the list elements and delimiters can
- have intervening whitespace, and comments, but this can be
- overridden by passing ``combine=True`` in the constructor. If
- ``combine`` is set to ``True``, the matching tokens are
- returned as a single token string, with the delimiters included;
- otherwise, the matching tokens are returned as a list of tokens,
- with the delimiters suppressed.
-
- Example::
-
- delimitedList(Word(alphas)).parseString("aa,bb,cc") # -> ['aa', 'bb', 'cc']
- delimitedList(Word(hexnums), delim=':', combine=True).parseString("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE']
- """
- dlName = _ustr(expr) + " [" + _ustr(delim) + " " + _ustr(expr) + "]..."
- if combine:
- return Combine(expr + ZeroOrMore(delim + expr)).setName(dlName)
- else:
- return (expr + ZeroOrMore(Suppress(delim) + expr)).setName(dlName)
-
-def countedArray(expr, intExpr=None):
- """Helper to define a counted list of expressions.
-
- This helper defines a pattern of the form::
-
- integer expr expr expr...
-
- where the leading integer tells how many expr expressions follow.
- The matched tokens returns the array of expr tokens as a list - the
- leading count token is suppressed.
-
- If ``intExpr`` is specified, it should be a pyparsing expression
- that produces an integer value.
-
- Example::
-
- countedArray(Word(alphas)).parseString('2 ab cd ef') # -> ['ab', 'cd']
-
- # in this parser, the leading integer value is given in binary,
- # '10' indicating that 2 values are in the array
- binaryConstant = Word('01').setParseAction(lambda t: int(t[0], 2))
- countedArray(Word(alphas), intExpr=binaryConstant).parseString('10 ab cd ef') # -> ['ab', 'cd']
- """
- arrayExpr = Forward()
- def countFieldParseAction(s, l, t):
- n = t[0]
- arrayExpr << (n and Group(And([expr] * n)) or Group(empty))
- return []
- if intExpr is None:
- intExpr = Word(nums).setParseAction(lambda t: int(t[0]))
- else:
- intExpr = intExpr.copy()
- intExpr.setName("arrayLen")
- intExpr.addParseAction(countFieldParseAction, callDuringTry=True)
- return (intExpr + arrayExpr).setName('(len) ' + _ustr(expr) + '...')
-
-def _flatten(L):
- ret = []
- for i in L:
- if isinstance(i, list):
- ret.extend(_flatten(i))
- else:
- ret.append(i)
- return ret
-
-def matchPreviousLiteral(expr):
- """Helper to define an expression that is indirectly defined from
- the tokens matched in a previous expression, that is, it looks for
- a 'repeat' of a previous expression. For example::
-
- first = Word(nums)
- second = matchPreviousLiteral(first)
- matchExpr = first + ":" + second
-
- will match ``"1:1"``, but not ``"1:2"``. Because this
- matches a previous literal, will also match the leading
- ``"1:1"`` in ``"1:10"``. If this is not desired, use
- :class:`matchPreviousExpr`. Do *not* use with packrat parsing
- enabled.
- """
- rep = Forward()
- def copyTokenToRepeater(s, l, t):
- if t:
- if len(t) == 1:
- rep << t[0]
- else:
- # flatten t tokens
- tflat = _flatten(t.asList())
- rep << And(Literal(tt) for tt in tflat)
- else:
- rep << Empty()
- expr.addParseAction(copyTokenToRepeater, callDuringTry=True)
- rep.setName('(prev) ' + _ustr(expr))
- return rep
-
-def matchPreviousExpr(expr):
- """Helper to define an expression that is indirectly defined from
- the tokens matched in a previous expression, that is, it looks for
- a 'repeat' of a previous expression. For example::
-
- first = Word(nums)
- second = matchPreviousExpr(first)
- matchExpr = first + ":" + second
-
- will match ``"1:1"``, but not ``"1:2"``. Because this
- matches by expressions, will *not* match the leading ``"1:1"``
- in ``"1:10"``; the expressions are evaluated first, and then
- compared, so ``"1"`` is compared with ``"10"``. Do *not* use
- with packrat parsing enabled.
- """
- rep = Forward()
- e2 = expr.copy()
- rep <<= e2
- def copyTokenToRepeater(s, l, t):
- matchTokens = _flatten(t.asList())
- def mustMatchTheseTokens(s, l, t):
- theseTokens = _flatten(t.asList())
- if theseTokens != matchTokens:
- raise ParseException('', 0, '')
- rep.setParseAction(mustMatchTheseTokens, callDuringTry=True)
- expr.addParseAction(copyTokenToRepeater, callDuringTry=True)
- rep.setName('(prev) ' + _ustr(expr))
- return rep
-
-def _escapeRegexRangeChars(s):
- # ~ escape these chars: ^-[]
- for c in r"\^-[]":
- s = s.replace(c, _bslash + c)
- s = s.replace("\n", r"\n")
- s = s.replace("\t", r"\t")
- return _ustr(s)
-
-def oneOf(strs, caseless=False, useRegex=True, asKeyword=False):
- """Helper to quickly define a set of alternative Literals, and makes
- sure to do longest-first testing when there is a conflict,
- regardless of the input order, but returns
- a :class:`MatchFirst` for best performance.
-
- Parameters:
-
- - strs - a string of space-delimited literals, or a collection of
- string literals
- - caseless - (default= ``False``) - treat all literals as
- caseless
- - useRegex - (default= ``True``) - as an optimization, will
- generate a Regex object; otherwise, will generate
- a :class:`MatchFirst` object (if ``caseless=True`` or ``asKeyword=True``, or if
- creating a :class:`Regex` raises an exception)
- - asKeyword - (default=``False``) - enforce Keyword-style matching on the
- generated expressions
-
- Example::
-
- comp_oper = oneOf("< = > <= >= !=")
- var = Word(alphas)
- number = Word(nums)
- term = var | number
- comparison_expr = term + comp_oper + term
- print(comparison_expr.searchString("B = 12 AA=23 B<=AA AA>12"))
-
- prints::
-
- [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']]
- """
- if isinstance(caseless, basestring):
- warnings.warn("More than one string argument passed to oneOf, pass "
- "choices as a list or space-delimited string", stacklevel=2)
-
- if caseless:
- isequal = (lambda a, b: a.upper() == b.upper())
- masks = (lambda a, b: b.upper().startswith(a.upper()))
- parseElementClass = CaselessKeyword if asKeyword else CaselessLiteral
- else:
- isequal = (lambda a, b: a == b)
- masks = (lambda a, b: b.startswith(a))
- parseElementClass = Keyword if asKeyword else Literal
-
- symbols = []
- if isinstance(strs, basestring):
- symbols = strs.split()
- elif isinstance(strs, Iterable):
- symbols = list(strs)
- else:
- warnings.warn("Invalid argument to oneOf, expected string or iterable",
- SyntaxWarning, stacklevel=2)
- if not symbols:
- return NoMatch()
-
- if not asKeyword:
- # if not producing keywords, need to reorder to take care to avoid masking
- # longer choices with shorter ones
- i = 0
- while i < len(symbols) - 1:
- cur = symbols[i]
- for j, other in enumerate(symbols[i + 1:]):
- if isequal(other, cur):
- del symbols[i + j + 1]
- break
- elif masks(cur, other):
- del symbols[i + j + 1]
- symbols.insert(i, other)
- break
- else:
- i += 1
-
- if not (caseless or asKeyword) and useRegex:
- # ~ print (strs, "->", "|".join([_escapeRegexChars(sym) for sym in symbols]))
- try:
- if len(symbols) == len("".join(symbols)):
- return Regex("[%s]" % "".join(_escapeRegexRangeChars(sym) for sym in symbols)).setName(' | '.join(symbols))
- else:
- return Regex("|".join(re.escape(sym) for sym in symbols)).setName(' | '.join(symbols))
- except Exception:
- warnings.warn("Exception creating Regex for oneOf, building MatchFirst",
- SyntaxWarning, stacklevel=2)
-
- # last resort, just use MatchFirst
- return MatchFirst(parseElementClass(sym) for sym in symbols).setName(' | '.join(symbols))
-
-def dictOf(key, value):
- """Helper to easily and clearly define a dictionary by specifying
- the respective patterns for the key and value. Takes care of
- defining the :class:`Dict`, :class:`ZeroOrMore`, and
- :class:`Group` tokens in the proper order. The key pattern
- can include delimiting markers or punctuation, as long as they are
- suppressed, thereby leaving the significant key text. The value
- pattern can include named results, so that the :class:`Dict` results
- can include named token fields.
-
- Example::
-
- text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
- attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
- print(OneOrMore(attr_expr).parseString(text).dump())
-
- attr_label = label
- attr_value = Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)
-
- # similar to Dict, but simpler call format
- result = dictOf(attr_label, attr_value).parseString(text)
- print(result.dump())
- print(result['shape'])
- print(result.shape) # object attribute access works too
- print(result.asDict())
-
- prints::
-
- [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
- - color: light blue
- - posn: upper left
- - shape: SQUARE
- - texture: burlap
- SQUARE
- SQUARE
- {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'}
- """
- return Dict(OneOrMore(Group(key + value)))
-
-def originalTextFor(expr, asString=True):
- """Helper to return the original, untokenized text for a given
- expression. Useful to restore the parsed fields of an HTML start
- tag into the raw tag text itself, or to revert separate tokens with
- intervening whitespace back to the original matching input text. By
- default, returns astring containing the original parsed text.
-
- If the optional ``asString`` argument is passed as
- ``False``, then the return value is
- a :class:`ParseResults` containing any results names that
- were originally matched, and a single token containing the original
- matched text from the input string. So if the expression passed to
- :class:`originalTextFor` contains expressions with defined
- results names, you must set ``asString`` to ``False`` if you
- want to preserve those results name values.
-
- Example::
-
- src = "this is test <b> bold <i>text</i> </b> normal text "
- for tag in ("b", "i"):
- opener, closer = makeHTMLTags(tag)
- patt = originalTextFor(opener + SkipTo(closer) + closer)
- print(patt.searchString(src)[0])
-
- prints::
-
- ['<b> bold <i>text</i> </b>']
- ['<i>text</i>']
- """
- locMarker = Empty().setParseAction(lambda s, loc, t: loc)
- endlocMarker = locMarker.copy()
- endlocMarker.callPreparse = False
- matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end")
- if asString:
- extractText = lambda s, l, t: s[t._original_start: t._original_end]
- else:
- def extractText(s, l, t):
- t[:] = [s[t.pop('_original_start'):t.pop('_original_end')]]
- matchExpr.setParseAction(extractText)
- matchExpr.ignoreExprs = expr.ignoreExprs
- return matchExpr
-
-def ungroup(expr):
- """Helper to undo pyparsing's default grouping of And expressions,
- even if all but one are non-empty.
- """
- return TokenConverter(expr).addParseAction(lambda t: t[0])
-
-def locatedExpr(expr):
- """Helper to decorate a returned token with its starting and ending
- locations in the input string.
-
- This helper adds the following results names:
-
- - locn_start = location where matched expression begins
- - locn_end = location where matched expression ends
- - value = the actual parsed results
-
- Be careful if the input text contains ``<TAB>`` characters, you
- may want to call :class:`ParserElement.parseWithTabs`
-
- Example::
-
- wd = Word(alphas)
- for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"):
- print(match)
-
- prints::
-
- [[0, 'ljsdf', 5]]
- [[8, 'lksdjjf', 15]]
- [[18, 'lkkjj', 23]]
- """
- locator = Empty().setParseAction(lambda s, l, t: l)
- return Group(locator("locn_start") + expr("value") + locator.copy().leaveWhitespace()("locn_end"))
-
-
-# convenience constants for positional expressions
-empty = Empty().setName("empty")
-lineStart = LineStart().setName("lineStart")
-lineEnd = LineEnd().setName("lineEnd")
-stringStart = StringStart().setName("stringStart")
-stringEnd = StringEnd().setName("stringEnd")
-
-_escapedPunc = Word(_bslash, r"\[]-*.$+^?()~ ", exact=2).setParseAction(lambda s, l, t: t[0][1])
-_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s, l, t: unichr(int(t[0].lstrip(r'\0x'), 16)))
-_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s, l, t: unichr(int(t[0][1:], 8)))
-_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r'\]', exact=1)
-_charRange = Group(_singleChar + Suppress("-") + _singleChar)
-_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group(OneOrMore(_charRange | _singleChar)).setResultsName("body") + "]"
-
-def srange(s):
- r"""Helper to easily define string ranges for use in Word
- construction. Borrows syntax from regexp '[]' string range
- definitions::
-
- srange("[0-9]") -> "0123456789"
- srange("[a-z]") -> "abcdefghijklmnopqrstuvwxyz"
- srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_"
-
- The input string must be enclosed in []'s, and the returned string
- is the expanded character set joined into a single string. The
- values enclosed in the []'s may be:
-
- - a single character
- - an escaped character with a leading backslash (such as ``\-``
- or ``\]``)
- - an escaped hex character with a leading ``'\x'``
- (``\x21``, which is a ``'!'`` character) (``\0x##``
- is also supported for backwards compatibility)
- - an escaped octal character with a leading ``'\0'``
- (``\041``, which is a ``'!'`` character)
- - a range of any of the above, separated by a dash (``'a-z'``,
- etc.)
- - any combination of the above (``'aeiouy'``,
- ``'a-zA-Z0-9_$'``, etc.)
- """
- _expanded = lambda p: p if not isinstance(p, ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]), ord(p[1]) + 1))
- try:
- return "".join(_expanded(part) for part in _reBracketExpr.parseString(s).body)
- except Exception:
- return ""
-
-def matchOnlyAtCol(n):
- """Helper method for defining parse actions that require matching at
- a specific column in the input text.
- """
- def verifyCol(strg, locn, toks):
- if col(locn, strg) != n:
- raise ParseException(strg, locn, "matched token not at column %d" % n)
- return verifyCol
-
-def replaceWith(replStr):
- """Helper method for common parse actions that simply return
- a literal value. Especially useful when used with
- :class:`transformString<ParserElement.transformString>` ().
-
- Example::
-
- num = Word(nums).setParseAction(lambda toks: int(toks[0]))
- na = oneOf("N/A NA").setParseAction(replaceWith(math.nan))
- term = na | num
-
- OneOrMore(term).parseString("324 234 N/A 234") # -> [324, 234, nan, 234]
- """
- return lambda s, l, t: [replStr]
-
-def removeQuotes(s, l, t):
- """Helper parse action for removing quotation marks from parsed
- quoted strings.
-
- Example::
-
- # by default, quotation marks are included in parsed results
- quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"]
-
- # use removeQuotes to strip quotation marks from parsed results
- quotedString.setParseAction(removeQuotes)
- quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"]
- """
- return t[0][1:-1]
-
-def tokenMap(func, *args):
- """Helper to define a parse action by mapping a function to all
- elements of a ParseResults list. If any additional args are passed,
- they are forwarded to the given function as additional arguments
- after the token, as in
- ``hex_integer = Word(hexnums).setParseAction(tokenMap(int, 16))``,
- which will convert the parsed data to an integer using base 16.
-
- Example (compare the last to example in :class:`ParserElement.transformString`::
-
- hex_ints = OneOrMore(Word(hexnums)).setParseAction(tokenMap(int, 16))
- hex_ints.runTests('''
- 00 11 22 aa FF 0a 0d 1a
- ''')
-
- upperword = Word(alphas).setParseAction(tokenMap(str.upper))
- OneOrMore(upperword).runTests('''
- my kingdom for a horse
- ''')
-
- wd = Word(alphas).setParseAction(tokenMap(str.title))
- OneOrMore(wd).setParseAction(' '.join).runTests('''
- now is the winter of our discontent made glorious summer by this sun of york
- ''')
-
- prints::
-
- 00 11 22 aa FF 0a 0d 1a
- [0, 17, 34, 170, 255, 10, 13, 26]
-
- my kingdom for a horse
- ['MY', 'KINGDOM', 'FOR', 'A', 'HORSE']
-
- now is the winter of our discontent made glorious summer by this sun of york
- ['Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York']
- """
- def pa(s, l, t):
- return [func(tokn, *args) for tokn in t]
-
- try:
- func_name = getattr(func, '__name__',
- getattr(func, '__class__').__name__)
- except Exception:
- func_name = str(func)
- pa.__name__ = func_name
-
- return pa
-
-upcaseTokens = tokenMap(lambda t: _ustr(t).upper())
-"""(Deprecated) Helper parse action to convert tokens to upper case.
-Deprecated in favor of :class:`pyparsing_common.upcaseTokens`"""
-
-downcaseTokens = tokenMap(lambda t: _ustr(t).lower())
-"""(Deprecated) Helper parse action to convert tokens to lower case.
-Deprecated in favor of :class:`pyparsing_common.downcaseTokens`"""
-
-def _makeTags(tagStr, xml,
- suppress_LT=Suppress("<"),
- suppress_GT=Suppress(">")):
- """Internal helper to construct opening and closing tag expressions, given a tag name"""
- if isinstance(tagStr, basestring):
- resname = tagStr
- tagStr = Keyword(tagStr, caseless=not xml)
- else:
- resname = tagStr.name
-
- tagAttrName = Word(alphas, alphanums + "_-:")
- if xml:
- tagAttrValue = dblQuotedString.copy().setParseAction(removeQuotes)
- openTag = (suppress_LT
- + tagStr("tag")
- + Dict(ZeroOrMore(Group(tagAttrName + Suppress("=") + tagAttrValue)))
- + Optional("/", default=[False])("empty").setParseAction(lambda s, l, t: t[0] == '/')
- + suppress_GT)
- else:
- tagAttrValue = quotedString.copy().setParseAction(removeQuotes) | Word(printables, excludeChars=">")
- openTag = (suppress_LT
- + tagStr("tag")
- + Dict(ZeroOrMore(Group(tagAttrName.setParseAction(downcaseTokens)
- + Optional(Suppress("=") + tagAttrValue))))
- + Optional("/", default=[False])("empty").setParseAction(lambda s, l, t: t[0] == '/')
- + suppress_GT)
- closeTag = Combine(_L("</") + tagStr + ">", adjacent=False)
-
- openTag.setName("<%s>" % resname)
- # add start<tagname> results name in parse action now that ungrouped names are not reported at two levels
- openTag.addParseAction(lambda t: t.__setitem__("start" + "".join(resname.replace(":", " ").title().split()), t.copy()))
- closeTag = closeTag("end" + "".join(resname.replace(":", " ").title().split())).setName("</%s>" % resname)
- openTag.tag = resname
- closeTag.tag = resname
- openTag.tag_body = SkipTo(closeTag())
- return openTag, closeTag
-
-def makeHTMLTags(tagStr):
- """Helper to construct opening and closing tag expressions for HTML,
- given a tag name. Matches tags in either upper or lower case,
- attributes with namespaces and with quoted or unquoted values.
-
- Example::
-
- text = '<td>More info at the <a href="https://github.com/pyparsing/pyparsing/wiki">pyparsing</a> wiki page</td>'
- # makeHTMLTags returns pyparsing expressions for the opening and
- # closing tags as a 2-tuple
- a, a_end = makeHTMLTags("A")
- link_expr = a + SkipTo(a_end)("link_text") + a_end
-
- for link in link_expr.searchString(text):
- # attributes in the <A> tag (like "href" shown here) are
- # also accessible as named results
- print(link.link_text, '->', link.href)
-
- prints::
-
- pyparsing -> https://github.com/pyparsing/pyparsing/wiki
- """
- return _makeTags(tagStr, False)
-
-def makeXMLTags(tagStr):
- """Helper to construct opening and closing tag expressions for XML,
- given a tag name. Matches tags only in the given upper/lower case.
-
- Example: similar to :class:`makeHTMLTags`
- """
- return _makeTags(tagStr, True)
-
-def withAttribute(*args, **attrDict):
- """Helper to create a validating parse action to be used with start
- tags created with :class:`makeXMLTags` or
- :class:`makeHTMLTags`. Use ``withAttribute`` to qualify
- a starting tag with a required attribute value, to avoid false
- matches on common tags such as ``<TD>`` or ``<DIV>``.
-
- Call ``withAttribute`` with a series of attribute names and
- values. Specify the list of filter attributes names and values as:
-
- - keyword arguments, as in ``(align="right")``, or
- - as an explicit dict with ``**`` operator, when an attribute
- name is also a Python reserved word, as in ``**{"class":"Customer", "align":"right"}``
- - a list of name-value tuples, as in ``(("ns1:class", "Customer"), ("ns2:align", "right"))``
-
- For attribute names with a namespace prefix, you must use the second
- form. Attribute names are matched insensitive to upper/lower case.
-
- If just testing for ``class`` (with or without a namespace), use
- :class:`withClass`.
-
- To verify that the attribute exists, but without specifying a value,
- pass ``withAttribute.ANY_VALUE`` as the value.
-
- Example::
-
- html = '''
- <div>
- Some text
- <div type="grid">1 4 0 1 0</div>
- <div type="graph">1,3 2,3 1,1</div>
- <div>this has no type</div>
- </div>
-
- '''
- div,div_end = makeHTMLTags("div")
-
- # only match div tag having a type attribute with value "grid"
- div_grid = div().setParseAction(withAttribute(type="grid"))
- grid_expr = div_grid + SkipTo(div | div_end)("body")
- for grid_header in grid_expr.searchString(html):
- print(grid_header.body)
-
- # construct a match with any div tag having a type attribute, regardless of the value
- div_any_type = div().setParseAction(withAttribute(type=withAttribute.ANY_VALUE))
- div_expr = div_any_type + SkipTo(div | div_end)("body")
- for div_header in div_expr.searchString(html):
- print(div_header.body)
-
- prints::
-
- 1 4 0 1 0
-
- 1 4 0 1 0
- 1,3 2,3 1,1
- """
- if args:
- attrs = args[:]
- else:
- attrs = attrDict.items()
- attrs = [(k, v) for k, v in attrs]
- def pa(s, l, tokens):
- for attrName, attrValue in attrs:
- if attrName not in tokens:
- raise ParseException(s, l, "no matching attribute " + attrName)
- if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue:
- raise ParseException(s, l, "attribute '%s' has value '%s', must be '%s'" %
- (attrName, tokens[attrName], attrValue))
- return pa
-withAttribute.ANY_VALUE = object()
-
-def withClass(classname, namespace=''):
- """Simplified version of :class:`withAttribute` when
- matching on a div class - made difficult because ``class`` is
- a reserved word in Python.
-
- Example::
-
- html = '''
- <div>
- Some text
- <div class="grid">1 4 0 1 0</div>
- <div class="graph">1,3 2,3 1,1</div>
- <div>this &lt;div&gt; has no class</div>
- </div>
-
- '''
- div,div_end = makeHTMLTags("div")
- div_grid = div().setParseAction(withClass("grid"))
-
- grid_expr = div_grid + SkipTo(div | div_end)("body")
- for grid_header in grid_expr.searchString(html):
- print(grid_header.body)
-
- div_any_type = div().setParseAction(withClass(withAttribute.ANY_VALUE))
- div_expr = div_any_type + SkipTo(div | div_end)("body")
- for div_header in div_expr.searchString(html):
- print(div_header.body)
-
- prints::
-
- 1 4 0 1 0
-
- 1 4 0 1 0
- 1,3 2,3 1,1
- """
- classattr = "%s:class" % namespace if namespace else "class"
- return withAttribute(**{classattr: classname})
-
-opAssoc = SimpleNamespace()
-opAssoc.LEFT = object()
-opAssoc.RIGHT = object()
-
-def infixNotation(baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')')):
- """Helper method for constructing grammars of expressions made up of
- operators working in a precedence hierarchy. Operators may be unary
- or binary, left- or right-associative. Parse actions can also be
- attached to operator expressions. The generated parser will also
- recognize the use of parentheses to override operator precedences
- (see example below).
-
- Note: if you define a deep operator list, you may see performance
- issues when using infixNotation. See
- :class:`ParserElement.enablePackrat` for a mechanism to potentially
- improve your parser performance.
-
- Parameters:
- - baseExpr - expression representing the most basic element for the
- nested
- - opList - list of tuples, one for each operator precedence level
- in the expression grammar; each tuple is of the form ``(opExpr,
- numTerms, rightLeftAssoc, parseAction)``, where:
-
- - opExpr is the pyparsing expression for the operator; may also
- be a string, which will be converted to a Literal; if numTerms
- is 3, opExpr is a tuple of two expressions, for the two
- operators separating the 3 terms
- - numTerms is the number of terms for this operator (must be 1,
- 2, or 3)
- - rightLeftAssoc is the indicator whether the operator is right
- or left associative, using the pyparsing-defined constants
- ``opAssoc.RIGHT`` and ``opAssoc.LEFT``.
- - parseAction is the parse action to be associated with
- expressions matching this operator expression (the parse action
- tuple member may be omitted); if the parse action is passed
- a tuple or list of functions, this is equivalent to calling
- ``setParseAction(*fn)``
- (:class:`ParserElement.setParseAction`)
- - lpar - expression for matching left-parentheses
- (default= ``Suppress('(')``)
- - rpar - expression for matching right-parentheses
- (default= ``Suppress(')')``)
-
- Example::
-
- # simple example of four-function arithmetic with ints and
- # variable names
- integer = pyparsing_common.signed_integer
- varname = pyparsing_common.identifier
-
- arith_expr = infixNotation(integer | varname,
- [
- ('-', 1, opAssoc.RIGHT),
- (oneOf('* /'), 2, opAssoc.LEFT),
- (oneOf('+ -'), 2, opAssoc.LEFT),
- ])
-
- arith_expr.runTests('''
- 5+3*6
- (5+3)*6
- -2--11
- ''', fullDump=False)
-
- prints::
-
- 5+3*6
- [[5, '+', [3, '*', 6]]]
-
- (5+3)*6
- [[[5, '+', 3], '*', 6]]
-
- -2--11
- [[['-', 2], '-', ['-', 11]]]
- """
- # captive version of FollowedBy that does not do parse actions or capture results names
- class _FB(FollowedBy):
- def parseImpl(self, instring, loc, doActions=True):
- self.expr.tryParse(instring, loc)
- return loc, []
-
- ret = Forward()
- lastExpr = baseExpr | (lpar + ret + rpar)
- for i, operDef in enumerate(opList):
- opExpr, arity, rightLeftAssoc, pa = (operDef + (None, ))[:4]
- termName = "%s term" % opExpr if arity < 3 else "%s%s term" % opExpr
- if arity == 3:
- if opExpr is None or len(opExpr) != 2:
- raise ValueError(
- "if numterms=3, opExpr must be a tuple or list of two expressions")
- opExpr1, opExpr2 = opExpr
- thisExpr = Forward().setName(termName)
- if rightLeftAssoc == opAssoc.LEFT:
- if arity == 1:
- matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + OneOrMore(opExpr))
- elif arity == 2:
- if opExpr is not None:
- matchExpr = _FB(lastExpr + opExpr + lastExpr) + Group(lastExpr + OneOrMore(opExpr + lastExpr))
- else:
- matchExpr = _FB(lastExpr + lastExpr) + Group(lastExpr + OneOrMore(lastExpr))
- elif arity == 3:
- matchExpr = (_FB(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr)
- + Group(lastExpr + OneOrMore(opExpr1 + lastExpr + opExpr2 + lastExpr)))
- else:
- raise ValueError("operator must be unary (1), binary (2), or ternary (3)")
- elif rightLeftAssoc == opAssoc.RIGHT:
- if arity == 1:
- # try to avoid LR with this extra test
- if not isinstance(opExpr, Optional):
- opExpr = Optional(opExpr)
- matchExpr = _FB(opExpr.expr + thisExpr) + Group(opExpr + thisExpr)
- elif arity == 2:
- if opExpr is not None:
- matchExpr = _FB(lastExpr + opExpr + thisExpr) + Group(lastExpr + OneOrMore(opExpr + thisExpr))
- else:
- matchExpr = _FB(lastExpr + thisExpr) + Group(lastExpr + OneOrMore(thisExpr))
- elif arity == 3:
- matchExpr = (_FB(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr)
- + Group(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr))
- else:
- raise ValueError("operator must be unary (1), binary (2), or ternary (3)")
- else:
- raise ValueError("operator must indicate right or left associativity")
- if pa:
- if isinstance(pa, (tuple, list)):
- matchExpr.setParseAction(*pa)
- else:
- matchExpr.setParseAction(pa)
- thisExpr <<= (matchExpr.setName(termName) | lastExpr)
- lastExpr = thisExpr
- ret <<= lastExpr
- return ret
-
-operatorPrecedence = infixNotation
-"""(Deprecated) Former name of :class:`infixNotation`, will be
-dropped in a future release."""
-
-dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*') + '"').setName("string enclosed in double quotes")
-sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "'").setName("string enclosed in single quotes")
-quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*') + '"'
- | Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "'").setName("quotedString using single or double quotes")
-unicodeString = Combine(_L('u') + quotedString.copy()).setName("unicode string literal")
-
-def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()):
- """Helper method for defining nested lists enclosed in opening and
- closing delimiters ("(" and ")" are the default).
-
- Parameters:
- - opener - opening character for a nested list
- (default= ``"("``); can also be a pyparsing expression
- - closer - closing character for a nested list
- (default= ``")"``); can also be a pyparsing expression
- - content - expression for items within the nested lists
- (default= ``None``)
- - ignoreExpr - expression for ignoring opening and closing
- delimiters (default= :class:`quotedString`)
-
- If an expression is not provided for the content argument, the
- nested expression will capture all whitespace-delimited content
- between delimiters as a list of separate values.
-
- Use the ``ignoreExpr`` argument to define expressions that may
- contain opening or closing characters that should not be treated as
- opening or closing characters for nesting, such as quotedString or
- a comment expression. Specify multiple expressions using an
- :class:`Or` or :class:`MatchFirst`. The default is
- :class:`quotedString`, but if no expressions are to be ignored, then
- pass ``None`` for this argument.
-
- Example::
-
- data_type = oneOf("void int short long char float double")
- decl_data_type = Combine(data_type + Optional(Word('*')))
- ident = Word(alphas+'_', alphanums+'_')
- number = pyparsing_common.number
- arg = Group(decl_data_type + ident)
- LPAR, RPAR = map(Suppress, "()")
-
- code_body = nestedExpr('{', '}', ignoreExpr=(quotedString | cStyleComment))
-
- c_function = (decl_data_type("type")
- + ident("name")
- + LPAR + Optional(delimitedList(arg), [])("args") + RPAR
- + code_body("body"))
- c_function.ignore(cStyleComment)
-
- source_code = '''
- int is_odd(int x) {
- return (x%2);
- }
-
- int dec_to_hex(char hchar) {
- if (hchar >= '0' && hchar <= '9') {
- return (ord(hchar)-ord('0'));
- } else {
- return (10+ord(hchar)-ord('A'));
- }
- }
- '''
- for func in c_function.searchString(source_code):
- print("%(name)s (%(type)s) args: %(args)s" % func)
-
-
- prints::
-
- is_odd (int) args: [['int', 'x']]
- dec_to_hex (int) args: [['char', 'hchar']]
- """
- if opener == closer:
- raise ValueError("opening and closing strings cannot be the same")
- if content is None:
- if isinstance(opener, basestring) and isinstance(closer, basestring):
- if len(opener) == 1 and len(closer) == 1:
- if ignoreExpr is not None:
- content = (Combine(OneOrMore(~ignoreExpr
- + CharsNotIn(opener
- + closer
- + ParserElement.DEFAULT_WHITE_CHARS, exact=1)
- )
- ).setParseAction(lambda t: t[0].strip()))
- else:
- content = (empty.copy() + CharsNotIn(opener
- + closer
- + ParserElement.DEFAULT_WHITE_CHARS
- ).setParseAction(lambda t: t[0].strip()))
- else:
- if ignoreExpr is not None:
- content = (Combine(OneOrMore(~ignoreExpr
- + ~Literal(opener)
- + ~Literal(closer)
- + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1))
- ).setParseAction(lambda t: t[0].strip()))
- else:
- content = (Combine(OneOrMore(~Literal(opener)
- + ~Literal(closer)
- + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1))
- ).setParseAction(lambda t: t[0].strip()))
- else:
- raise ValueError("opening and closing arguments must be strings if no content expression is given")
- ret = Forward()
- if ignoreExpr is not None:
- ret <<= Group(Suppress(opener) + ZeroOrMore(ignoreExpr | ret | content) + Suppress(closer))
- else:
- ret <<= Group(Suppress(opener) + ZeroOrMore(ret | content) + Suppress(closer))
- ret.setName('nested %s%s expression' % (opener, closer))
- return ret
-
-def indentedBlock(blockStatementExpr, indentStack, indent=True):
- """Helper method for defining space-delimited indentation blocks,
- such as those used to define block statements in Python source code.
-
- Parameters:
-
- - blockStatementExpr - expression defining syntax of statement that
- is repeated within the indented block
- - indentStack - list created by caller to manage indentation stack
- (multiple statementWithIndentedBlock expressions within a single
- grammar should share a common indentStack)
- - indent - boolean indicating whether block must be indented beyond
- the current level; set to False for block of left-most
- statements (default= ``True``)
-
- A valid block must contain at least one ``blockStatement``.
-
- Example::
-
- data = '''
- def A(z):
- A1
- B = 100
- G = A2
- A2
- A3
- B
- def BB(a,b,c):
- BB1
- def BBA():
- bba1
- bba2
- bba3
- C
- D
- def spam(x,y):
- def eggs(z):
- pass
- '''
-
-
- indentStack = [1]
- stmt = Forward()
-
- identifier = Word(alphas, alphanums)
- funcDecl = ("def" + identifier + Group("(" + Optional(delimitedList(identifier)) + ")") + ":")
- func_body = indentedBlock(stmt, indentStack)
- funcDef = Group(funcDecl + func_body)
-
- rvalue = Forward()
- funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")")
- rvalue << (funcCall | identifier | Word(nums))
- assignment = Group(identifier + "=" + rvalue)
- stmt << (funcDef | assignment | identifier)
-
- module_body = OneOrMore(stmt)
-
- parseTree = module_body.parseString(data)
- parseTree.pprint()
-
- prints::
-
- [['def',
- 'A',
- ['(', 'z', ')'],
- ':',
- [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]],
- 'B',
- ['def',
- 'BB',
- ['(', 'a', 'b', 'c', ')'],
- ':',
- [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]],
- 'C',
- 'D',
- ['def',
- 'spam',
- ['(', 'x', 'y', ')'],
- ':',
- [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]]
- """
- backup_stack = indentStack[:]
-
- def reset_stack():
- indentStack[:] = backup_stack
-
- def checkPeerIndent(s, l, t):
- if l >= len(s): return
- curCol = col(l, s)
- if curCol != indentStack[-1]:
- if curCol > indentStack[-1]:
- raise ParseException(s, l, "illegal nesting")
- raise ParseException(s, l, "not a peer entry")
-
- def checkSubIndent(s, l, t):
- curCol = col(l, s)
- if curCol > indentStack[-1]:
- indentStack.append(curCol)
- else:
- raise ParseException(s, l, "not a subentry")
-
- def checkUnindent(s, l, t):
- if l >= len(s): return
- curCol = col(l, s)
- if not(indentStack and curCol in indentStack):
- raise ParseException(s, l, "not an unindent")
- if curCol < indentStack[-1]:
- indentStack.pop()
-
- NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress(), stopOn=StringEnd())
- INDENT = (Empty() + Empty().setParseAction(checkSubIndent)).setName('INDENT')
- PEER = Empty().setParseAction(checkPeerIndent).setName('')
- UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT')
- if indent:
- smExpr = Group(Optional(NL)
- + INDENT
- + OneOrMore(PEER + Group(blockStatementExpr) + Optional(NL), stopOn=StringEnd())
- + UNDENT)
- else:
- smExpr = Group(Optional(NL)
- + OneOrMore(PEER + Group(blockStatementExpr) + Optional(NL), stopOn=StringEnd())
- + UNDENT)
- smExpr.setFailAction(lambda a, b, c, d: reset_stack())
- blockStatementExpr.ignore(_bslash + LineEnd())
- return smExpr.setName('indented block')
-
-alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]")
-punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]")
-
-anyOpenTag, anyCloseTag = makeHTMLTags(Word(alphas, alphanums + "_:").setName('any tag'))
-_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(), '><& "\''))
-commonHTMLEntity = Regex('&(?P<entity>' + '|'.join(_htmlEntityMap.keys()) +");").setName("common HTML entity")
-def replaceHTMLEntity(t):
- """Helper parser action to replace common HTML entities with their special characters"""
- return _htmlEntityMap.get(t.entity)
-
-# it's easy to get these comment structures wrong - they're very common, so may as well make them available
-cStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/').setName("C style comment")
-"Comment of the form ``/* ... */``"
-
-htmlComment = Regex(r"<!--[\s\S]*?-->").setName("HTML comment")
-"Comment of the form ``<!-- ... -->``"
-
-restOfLine = Regex(r".*").leaveWhitespace().setName("rest of line")
-dblSlashComment = Regex(r"//(?:\\\n|[^\n])*").setName("// comment")
-"Comment of the form ``// ... (to end of line)``"
-
-cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/' | dblSlashComment).setName("C++ style comment")
-"Comment of either form :class:`cStyleComment` or :class:`dblSlashComment`"
-
-javaStyleComment = cppStyleComment
-"Same as :class:`cppStyleComment`"
-
-pythonStyleComment = Regex(r"#.*").setName("Python style comment")
-"Comment of the form ``# ... (to end of line)``"
-
-_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',')
- + Optional(Word(" \t")
- + ~Literal(",") + ~LineEnd()))).streamline().setName("commaItem")
-commaSeparatedList = delimitedList(Optional(quotedString.copy() | _commasepitem, default="")).setName("commaSeparatedList")
-"""(Deprecated) Predefined expression of 1 or more printable words or
-quoted strings, separated by commas.
-
-This expression is deprecated in favor of :class:`pyparsing_common.comma_separated_list`.
-"""
-
-# some other useful expressions - using lower-case class name since we are really using this as a namespace
-class pyparsing_common:
- """Here are some common low-level expressions that may be useful in
- jump-starting parser development:
-
- - numeric forms (:class:`integers<integer>`, :class:`reals<real>`,
- :class:`scientific notation<sci_real>`)
- - common :class:`programming identifiers<identifier>`
- - network addresses (:class:`MAC<mac_address>`,
- :class:`IPv4<ipv4_address>`, :class:`IPv6<ipv6_address>`)
- - ISO8601 :class:`dates<iso8601_date>` and
- :class:`datetime<iso8601_datetime>`
- - :class:`UUID<uuid>`
- - :class:`comma-separated list<comma_separated_list>`
-
- Parse actions:
-
- - :class:`convertToInteger`
- - :class:`convertToFloat`
- - :class:`convertToDate`
- - :class:`convertToDatetime`
- - :class:`stripHTMLTags`
- - :class:`upcaseTokens`
- - :class:`downcaseTokens`
-
- Example::
-
- pyparsing_common.number.runTests('''
- # any int or real number, returned as the appropriate type
- 100
- -100
- +100
- 3.14159
- 6.02e23
- 1e-12
- ''')
-
- pyparsing_common.fnumber.runTests('''
- # any int or real number, returned as float
- 100
- -100
- +100
- 3.14159
- 6.02e23
- 1e-12
- ''')
-
- pyparsing_common.hex_integer.runTests('''
- # hex numbers
- 100
- FF
- ''')
-
- pyparsing_common.fraction.runTests('''
- # fractions
- 1/2
- -3/4
- ''')
-
- pyparsing_common.mixed_integer.runTests('''
- # mixed fractions
- 1
- 1/2
- -3/4
- 1-3/4
- ''')
-
- import uuid
- pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID))
- pyparsing_common.uuid.runTests('''
- # uuid
- 12345678-1234-5678-1234-567812345678
- ''')
-
- prints::
-
- # any int or real number, returned as the appropriate type
- 100
- [100]
-
- -100
- [-100]
-
- +100
- [100]
-
- 3.14159
- [3.14159]
-
- 6.02e23
- [6.02e+23]
-
- 1e-12
- [1e-12]
-
- # any int or real number, returned as float
- 100
- [100.0]
-
- -100
- [-100.0]
-
- +100
- [100.0]
-
- 3.14159
- [3.14159]
-
- 6.02e23
- [6.02e+23]
-
- 1e-12
- [1e-12]
-
- # hex numbers
- 100
- [256]
-
- FF
- [255]
-
- # fractions
- 1/2
- [0.5]
-
- -3/4
- [-0.75]
-
- # mixed fractions
- 1
- [1]
-
- 1/2
- [0.5]
-
- -3/4
- [-0.75]
-
- 1-3/4
- [1.75]
-
- # uuid
- 12345678-1234-5678-1234-567812345678
- [UUID('12345678-1234-5678-1234-567812345678')]
- """
-
- convertToInteger = tokenMap(int)
- """
- Parse action for converting parsed integers to Python int
- """
-
- convertToFloat = tokenMap(float)
- """
- Parse action for converting parsed numbers to Python float
- """
-
- integer = Word(nums).setName("integer").setParseAction(convertToInteger)
- """expression that parses an unsigned integer, returns an int"""
-
- hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int, 16))
- """expression that parses a hexadecimal integer, returns an int"""
-
- signed_integer = Regex(r'[+-]?\d+').setName("signed integer").setParseAction(convertToInteger)
- """expression that parses an integer with optional leading sign, returns an int"""
-
- fraction = (signed_integer().setParseAction(convertToFloat) + '/' + signed_integer().setParseAction(convertToFloat)).setName("fraction")
- """fractional expression of an integer divided by an integer, returns a float"""
- fraction.addParseAction(lambda t: t[0]/t[-1])
-
- mixed_integer = (fraction | signed_integer + Optional(Optional('-').suppress() + fraction)).setName("fraction or mixed integer-fraction")
- """mixed integer of the form 'integer - fraction', with optional leading integer, returns float"""
- mixed_integer.addParseAction(sum)
-
- real = Regex(r'[+-]?(?:\d+\.\d*|\.\d+)').setName("real number").setParseAction(convertToFloat)
- """expression that parses a floating point number and returns a float"""
-
- sci_real = Regex(r'[+-]?(?:\d+(?:[eE][+-]?\d+)|(?:\d+\.\d*|\.\d+)(?:[eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat)
- """expression that parses a floating point number with optional
- scientific notation and returns a float"""
-
- # streamlining this expression makes the docs nicer-looking
- number = (sci_real | real | signed_integer).streamline()
- """any numeric expression, returns the corresponding Python type"""
-
- fnumber = Regex(r'[+-]?\d+\.?\d*([eE][+-]?\d+)?').setName("fnumber").setParseAction(convertToFloat)
- """any int or real number, returned as float"""
-
- identifier = Word(alphas + '_', alphanums + '_').setName("identifier")
- """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')"""
-
- ipv4_address = Regex(r'(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}').setName("IPv4 address")
- "IPv4 address (``0.0.0.0 - 255.255.255.255``)"
-
- _ipv6_part = Regex(r'[0-9a-fA-F]{1,4}').setName("hex_integer")
- _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part) * 7).setName("full IPv6 address")
- _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part) * (0, 6))
- + "::"
- + Optional(_ipv6_part + (':' + _ipv6_part) * (0, 6))
- ).setName("short IPv6 address")
- _short_ipv6_address.addCondition(lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8)
- _mixed_ipv6_address = ("::ffff:" + ipv4_address).setName("mixed IPv6 address")
- ipv6_address = Combine((_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).setName("IPv6 address")).setName("IPv6 address")
- "IPv6 address (long, short, or mixed form)"
-
- mac_address = Regex(r'[0-9a-fA-F]{2}([:.-])[0-9a-fA-F]{2}(?:\1[0-9a-fA-F]{2}){4}').setName("MAC address")
- "MAC address xx:xx:xx:xx:xx (may also have '-' or '.' delimiters)"
-
- @staticmethod
- def convertToDate(fmt="%Y-%m-%d"):
- """
- Helper to create a parse action for converting parsed date string to Python datetime.date
-
- Params -
- - fmt - format to be passed to datetime.strptime (default= ``"%Y-%m-%d"``)
-
- Example::
-
- date_expr = pyparsing_common.iso8601_date.copy()
- date_expr.setParseAction(pyparsing_common.convertToDate())
- print(date_expr.parseString("1999-12-31"))
-
- prints::
-
- [datetime.date(1999, 12, 31)]
- """
- def cvt_fn(s, l, t):
- try:
- return datetime.strptime(t[0], fmt).date()
- except ValueError as ve:
- raise ParseException(s, l, str(ve))
- return cvt_fn
-
- @staticmethod
- def convertToDatetime(fmt="%Y-%m-%dT%H:%M:%S.%f"):
- """Helper to create a parse action for converting parsed
- datetime string to Python datetime.datetime
-
- Params -
- - fmt - format to be passed to datetime.strptime (default= ``"%Y-%m-%dT%H:%M:%S.%f"``)
-
- Example::
-
- dt_expr = pyparsing_common.iso8601_datetime.copy()
- dt_expr.setParseAction(pyparsing_common.convertToDatetime())
- print(dt_expr.parseString("1999-12-31T23:59:59.999"))
-
- prints::
-
- [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)]
- """
- def cvt_fn(s, l, t):
- try:
- return datetime.strptime(t[0], fmt)
- except ValueError as ve:
- raise ParseException(s, l, str(ve))
- return cvt_fn
-
- iso8601_date = Regex(r'(?P<year>\d{4})(?:-(?P<month>\d\d)(?:-(?P<day>\d\d))?)?').setName("ISO8601 date")
- "ISO8601 date (``yyyy-mm-dd``)"
-
- iso8601_datetime = Regex(r'(?P<year>\d{4})-(?P<month>\d\d)-(?P<day>\d\d)[T ](?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d(\.\d*)?)?)?(?P<tz>Z|[+-]\d\d:?\d\d)?').setName("ISO8601 datetime")
- "ISO8601 datetime (``yyyy-mm-ddThh:mm:ss.s(Z|+-00:00)``) - trailing seconds, milliseconds, and timezone optional; accepts separating ``'T'`` or ``' '``"
-
- uuid = Regex(r'[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}').setName("UUID")
- "UUID (``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx``)"
-
- _html_stripper = anyOpenTag.suppress() | anyCloseTag.suppress()
- @staticmethod
- def stripHTMLTags(s, l, tokens):
- """Parse action to remove HTML tags from web page HTML source
-
- Example::
-
- # strip HTML links from normal text
- text = '<td>More info at the <a href="https://github.com/pyparsing/pyparsing/wiki">pyparsing</a> wiki page</td>'
- td, td_end = makeHTMLTags("TD")
- table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end
- print(table_text.parseString(text).body)
-
- Prints::
-
- More info at the pyparsing wiki page
- """
- return pyparsing_common._html_stripper.transformString(tokens[0])
-
- _commasepitem = Combine(OneOrMore(~Literal(",")
- + ~LineEnd()
- + Word(printables, excludeChars=',')
- + Optional(White(" \t")))).streamline().setName("commaItem")
- comma_separated_list = delimitedList(Optional(quotedString.copy()
- | _commasepitem, default='')
- ).setName("comma separated list")
- """Predefined expression of 1 or more printable words or quoted strings, separated by commas."""
-
- upcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).upper()))
- """Parse action to convert tokens to upper case."""
-
- downcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).lower()))
- """Parse action to convert tokens to lower case."""
-
-
-class _lazyclassproperty(object):
- def __init__(self, fn):
- self.fn = fn
- self.__doc__ = fn.__doc__
- self.__name__ = fn.__name__
-
- def __get__(self, obj, cls):
- if cls is None:
- cls = type(obj)
- if not hasattr(cls, '_intern') or any(cls._intern is getattr(superclass, '_intern', [])
- for superclass in cls.__mro__[1:]):
- cls._intern = {}
- attrname = self.fn.__name__
- if attrname not in cls._intern:
- cls._intern[attrname] = self.fn(cls)
- return cls._intern[attrname]
-
-
-class unicode_set(object):
- """
- A set of Unicode characters, for language-specific strings for
- ``alphas``, ``nums``, ``alphanums``, and ``printables``.
- A unicode_set is defined by a list of ranges in the Unicode character
- set, in a class attribute ``_ranges``, such as::
-
- _ranges = [(0x0020, 0x007e), (0x00a0, 0x00ff),]
-
- A unicode set can also be defined using multiple inheritance of other unicode sets::
-
- class CJK(Chinese, Japanese, Korean):
- pass
- """
- _ranges = []
-
- @classmethod
- def _get_chars_for_ranges(cls):
- ret = []
- for cc in cls.__mro__:
- if cc is unicode_set:
- break
- for rr in cc._ranges:
- ret.extend(range(rr[0], rr[-1] + 1))
- return [unichr(c) for c in sorted(set(ret))]
-
- @_lazyclassproperty
- def printables(cls):
- "all non-whitespace characters in this range"
- return u''.join(filterfalse(unicode.isspace, cls._get_chars_for_ranges()))
-
- @_lazyclassproperty
- def alphas(cls):
- "all alphabetic characters in this range"
- return u''.join(filter(unicode.isalpha, cls._get_chars_for_ranges()))
-
- @_lazyclassproperty
- def nums(cls):
- "all numeric digit characters in this range"
- return u''.join(filter(unicode.isdigit, cls._get_chars_for_ranges()))
-
- @_lazyclassproperty
- def alphanums(cls):
- "all alphanumeric characters in this range"
- return cls.alphas + cls.nums
-
-
-class pyparsing_unicode(unicode_set):
- """
- A namespace class for defining common language unicode_sets.
- """
- _ranges = [(32, sys.maxunicode)]
-
- class Latin1(unicode_set):
- "Unicode set for Latin-1 Unicode Character Range"
- _ranges = [(0x0020, 0x007e), (0x00a0, 0x00ff),]
-
- class LatinA(unicode_set):
- "Unicode set for Latin-A Unicode Character Range"
- _ranges = [(0x0100, 0x017f),]
-
- class LatinB(unicode_set):
- "Unicode set for Latin-B Unicode Character Range"
- _ranges = [(0x0180, 0x024f),]
-
- class Greek(unicode_set):
- "Unicode set for Greek Unicode Character Ranges"
- _ranges = [
- (0x0370, 0x03ff), (0x1f00, 0x1f15), (0x1f18, 0x1f1d), (0x1f20, 0x1f45), (0x1f48, 0x1f4d),
- (0x1f50, 0x1f57), (0x1f59,), (0x1f5b,), (0x1f5d,), (0x1f5f, 0x1f7d), (0x1f80, 0x1fb4), (0x1fb6, 0x1fc4),
- (0x1fc6, 0x1fd3), (0x1fd6, 0x1fdb), (0x1fdd, 0x1fef), (0x1ff2, 0x1ff4), (0x1ff6, 0x1ffe),
- ]
-
- class Cyrillic(unicode_set):
- "Unicode set for Cyrillic Unicode Character Range"
- _ranges = [(0x0400, 0x04ff)]
-
- class Chinese(unicode_set):
- "Unicode set for Chinese Unicode Character Range"
- _ranges = [(0x4e00, 0x9fff), (0x3000, 0x303f),]
-
- class Japanese(unicode_set):
- "Unicode set for Japanese Unicode Character Range, combining Kanji, Hiragana, and Katakana ranges"
- _ranges = []
-
- class Kanji(unicode_set):
- "Unicode set for Kanji Unicode Character Range"
- _ranges = [(0x4E00, 0x9Fbf), (0x3000, 0x303f),]
-
- class Hiragana(unicode_set):
- "Unicode set for Hiragana Unicode Character Range"
- _ranges = [(0x3040, 0x309f),]
-
- class Katakana(unicode_set):
- "Unicode set for Katakana Unicode Character Range"
- _ranges = [(0x30a0, 0x30ff),]
-
- class Korean(unicode_set):
- "Unicode set for Korean Unicode Character Range"
- _ranges = [(0xac00, 0xd7af), (0x1100, 0x11ff), (0x3130, 0x318f), (0xa960, 0xa97f), (0xd7b0, 0xd7ff), (0x3000, 0x303f),]
-
- class CJK(Chinese, Japanese, Korean):
- "Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range"
- pass
-
- class Thai(unicode_set):
- "Unicode set for Thai Unicode Character Range"
- _ranges = [(0x0e01, 0x0e3a), (0x0e3f, 0x0e5b),]
-
- class Arabic(unicode_set):
- "Unicode set for Arabic Unicode Character Range"
- _ranges = [(0x0600, 0x061b), (0x061e, 0x06ff), (0x0700, 0x077f),]
-
- class Hebrew(unicode_set):
- "Unicode set for Hebrew Unicode Character Range"
- _ranges = [(0x0590, 0x05ff),]
-
- class Devanagari(unicode_set):
- "Unicode set for Devanagari Unicode Character Range"
- _ranges = [(0x0900, 0x097f), (0xa8e0, 0xa8ff)]
-
-pyparsing_unicode.Japanese._ranges = (pyparsing_unicode.Japanese.Kanji._ranges
- + pyparsing_unicode.Japanese.Hiragana._ranges
- + pyparsing_unicode.Japanese.Katakana._ranges)
-
-# define ranges in language character sets
-if PY_3:
- setattr(pyparsing_unicode, u"العربية", pyparsing_unicode.Arabic)
- setattr(pyparsing_unicode, u"中文", pyparsing_unicode.Chinese)
- setattr(pyparsing_unicode, u"кириллица", pyparsing_unicode.Cyrillic)
- setattr(pyparsing_unicode, u"Ελληνικά", pyparsing_unicode.Greek)
- setattr(pyparsing_unicode, u"עִברִית", pyparsing_unicode.Hebrew)
- setattr(pyparsing_unicode, u"日本語", pyparsing_unicode.Japanese)
- setattr(pyparsing_unicode.Japanese, u"漢字", pyparsing_unicode.Japanese.Kanji)
- setattr(pyparsing_unicode.Japanese, u"カタカナ", pyparsing_unicode.Japanese.Katakana)
- setattr(pyparsing_unicode.Japanese, u"ひらがな", pyparsing_unicode.Japanese.Hiragana)
- setattr(pyparsing_unicode, u"한국어", pyparsing_unicode.Korean)
- setattr(pyparsing_unicode, u"ไทย", pyparsing_unicode.Thai)
- setattr(pyparsing_unicode, u"देवनागरी", pyparsing_unicode.Devanagari)
-
-
-class pyparsing_test:
- """
- namespace class for classes useful in writing unit tests
- """
-
- class reset_pyparsing_context:
- """
- Context manager to be used when writing unit tests that modify pyparsing config values:
- - packrat parsing
- - default whitespace characters.
- - default keyword characters
- - literal string auto-conversion class
- - __diag__ settings
-
- Example:
- with reset_pyparsing_context():
- # test that literals used to construct a grammar are automatically suppressed
- ParserElement.inlineLiteralsUsing(Suppress)
-
- term = Word(alphas) | Word(nums)
- group = Group('(' + term[...] + ')')
-
- # assert that the '()' characters are not included in the parsed tokens
- self.assertParseAndCheckLisst(group, "(abc 123 def)", ['abc', '123', 'def'])
-
- # after exiting context manager, literals are converted to Literal expressions again
- """
-
- def __init__(self):
- self._save_context = {}
-
- def save(self):
- self._save_context["default_whitespace"] = ParserElement.DEFAULT_WHITE_CHARS
- self._save_context["default_keyword_chars"] = Keyword.DEFAULT_KEYWORD_CHARS
- self._save_context[
- "literal_string_class"
- ] = ParserElement._literalStringClass
- self._save_context["packrat_enabled"] = ParserElement._packratEnabled
- self._save_context["packrat_parse"] = ParserElement._parse
- self._save_context["__diag__"] = {
- name: getattr(__diag__, name) for name in __diag__._all_names
- }
- self._save_context["__compat__"] = {
- "collect_all_And_tokens": __compat__.collect_all_And_tokens
- }
- return self
-
- def restore(self):
- # reset pyparsing global state
- if (
- ParserElement.DEFAULT_WHITE_CHARS
- != self._save_context["default_whitespace"]
- ):
- ParserElement.setDefaultWhitespaceChars(
- self._save_context["default_whitespace"]
- )
- Keyword.DEFAULT_KEYWORD_CHARS = self._save_context["default_keyword_chars"]
- ParserElement.inlineLiteralsUsing(
- self._save_context["literal_string_class"]
- )
- for name, value in self._save_context["__diag__"].items():
- setattr(__diag__, name, value)
- ParserElement._packratEnabled = self._save_context["packrat_enabled"]
- ParserElement._parse = self._save_context["packrat_parse"]
- __compat__.collect_all_And_tokens = self._save_context["__compat__"]
-
- def __enter__(self):
- return self.save()
-
- def __exit__(self, *args):
- return self.restore()
-
- class TestParseResultsAsserts:
- """
- A mixin class to add parse results assertion methods to normal unittest.TestCase classes.
- """
- def assertParseResultsEquals(
- self, result, expected_list=None, expected_dict=None, msg=None
- ):
- """
- Unit test assertion to compare a ParseResults object with an optional expected_list,
- and compare any defined results names with an optional expected_dict.
- """
- if expected_list is not None:
- self.assertEqual(expected_list, result.asList(), msg=msg)
- if expected_dict is not None:
- self.assertEqual(expected_dict, result.asDict(), msg=msg)
-
- def assertParseAndCheckList(
- self, expr, test_string, expected_list, msg=None, verbose=True
- ):
- """
- Convenience wrapper assert to test a parser element and input string, and assert that
- the resulting ParseResults.asList() is equal to the expected_list.
- """
- result = expr.parseString(test_string, parseAll=True)
- if verbose:
- print(result.dump())
- self.assertParseResultsEquals(result, expected_list=expected_list, msg=msg)
-
- def assertParseAndCheckDict(
- self, expr, test_string, expected_dict, msg=None, verbose=True
- ):
- """
- Convenience wrapper assert to test a parser element and input string, and assert that
- the resulting ParseResults.asDict() is equal to the expected_dict.
- """
- result = expr.parseString(test_string, parseAll=True)
- if verbose:
- print(result.dump())
- self.assertParseResultsEquals(result, expected_dict=expected_dict, msg=msg)
-
- def assertRunTestResults(
- self, run_tests_report, expected_parse_results=None, msg=None
- ):
- """
- Unit test assertion to evaluate output of ParserElement.runTests(). If a list of
- list-dict tuples is given as the expected_parse_results argument, then these are zipped
- with the report tuples returned by runTests and evaluated using assertParseResultsEquals.
- Finally, asserts that the overall runTests() success value is True.
-
- :param run_tests_report: tuple(bool, [tuple(str, ParseResults or Exception)]) returned from runTests
- :param expected_parse_results (optional): [tuple(str, list, dict, Exception)]
- """
- run_test_success, run_test_results = run_tests_report
-
- if expected_parse_results is not None:
- merged = [
- (rpt[0], rpt[1], expected)
- for rpt, expected in zip(run_test_results, expected_parse_results)
- ]
- for test_string, result, expected in merged:
- # expected should be a tuple containing a list and/or a dict or an exception,
- # and optional failure message string
- # an empty tuple will skip any result validation
- fail_msg = next(
- (exp for exp in expected if isinstance(exp, str)), None
- )
- expected_exception = next(
- (
- exp
- for exp in expected
- if isinstance(exp, type) and issubclass(exp, Exception)
- ),
- None,
- )
- if expected_exception is not None:
- with self.assertRaises(
- expected_exception=expected_exception, msg=fail_msg or msg
- ):
- if isinstance(result, Exception):
- raise result
- else:
- expected_list = next(
- (exp for exp in expected if isinstance(exp, list)), None
- )
- expected_dict = next(
- (exp for exp in expected if isinstance(exp, dict)), None
- )
- if (expected_list, expected_dict) != (None, None):
- self.assertParseResultsEquals(
- result,
- expected_list=expected_list,
- expected_dict=expected_dict,
- msg=fail_msg or msg,
- )
- else:
- # warning here maybe?
- print("no validation for {!r}".format(test_string))
-
- # do this last, in case some specific test results can be reported instead
- self.assertTrue(
- run_test_success, msg=msg if msg is not None else "failed runTests"
- )
-
- @contextmanager
- def assertRaisesParseException(self, exc_type=ParseException, msg=None):
- with self.assertRaises(exc_type, msg=msg):
- yield
-
-
-if __name__ == "__main__":
-
- selectToken = CaselessLiteral("select")
- fromToken = CaselessLiteral("from")
-
- ident = Word(alphas, alphanums + "_$")
-
- columnName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens)
- columnNameList = Group(delimitedList(columnName)).setName("columns")
- columnSpec = ('*' | columnNameList)
-
- tableName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens)
- tableNameList = Group(delimitedList(tableName)).setName("tables")
-
- simpleSQL = selectToken("command") + columnSpec("columns") + fromToken + tableNameList("tables")
-
- # demo runTests method, including embedded comments in test string
- simpleSQL.runTests("""
- # '*' as column list and dotted table name
- select * from SYS.XYZZY
-
- # caseless match on "SELECT", and casts back to "select"
- SELECT * from XYZZY, ABC
-
- # list of column names, and mixed case SELECT keyword
- Select AA,BB,CC from Sys.dual
-
- # multiple tables
- Select A, B, C from Sys.dual, Table2
-
- # invalid SELECT keyword - should fail
- Xelect A, B, C from Sys.dual
-
- # incomplete command - should fail
- Select
-
- # invalid column name - should fail
- Select ^^^ frox Sys.dual
-
- """)
-
- pyparsing_common.number.runTests("""
- 100
- -100
- +100
- 3.14159
- 6.02e23
- 1e-12
- """)
-
- # any int or real number, returned as float
- pyparsing_common.fnumber.runTests("""
- 100
- -100
- +100
- 3.14159
- 6.02e23
- 1e-12
- """)
-
- pyparsing_common.hex_integer.runTests("""
- 100
- FF
- """)
-
- import uuid
- pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID))
- pyparsing_common.uuid.runTests("""
- 12345678-1234-5678-1234-567812345678
- """)
diff --git a/src/pip/_vendor/pyparsing.pyi b/src/pip/_vendor/pyparsing.pyi
deleted file mode 100644
index 8e9de6b02..000000000
--- a/src/pip/_vendor/pyparsing.pyi
+++ /dev/null
@@ -1 +0,0 @@
-from pyparsing import * \ No newline at end of file
diff --git a/src/pip/_vendor/pyparsing.LICENSE b/src/pip/_vendor/pyparsing/LICENSE
index 1bf98523e..1bf98523e 100644
--- a/src/pip/_vendor/pyparsing.LICENSE
+++ b/src/pip/_vendor/pyparsing/LICENSE
diff --git a/src/pip/_vendor/pyparsing/__init__.py b/src/pip/_vendor/pyparsing/__init__.py
new file mode 100644
index 000000000..75372500e
--- /dev/null
+++ b/src/pip/_vendor/pyparsing/__init__.py
@@ -0,0 +1,331 @@
+# module pyparsing.py
+#
+# Copyright (c) 2003-2022 Paul T. McGuire
+#
+# 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.
+#
+
+__doc__ = """
+pyparsing module - Classes and methods to define and execute parsing grammars
+=============================================================================
+
+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. With pyparsing, you don't need to learn
+a new syntax for defining grammars or matching expressions - the parsing
+module provides a library of classes that you use to construct the
+grammar directly in Python.
+
+Here is a program to parse "Hello, World!" (or any greeting of the form
+``"<salutation>, <addressee>!"``), built up using :class:`Word`,
+:class:`Literal`, and :class:`And` elements
+(the :meth:`'+'<ParserElement.__add__>` operators create :class:`And` expressions,
+and the strings are auto-converted to :class:`Literal` expressions)::
+
+ from pip._vendor.pyparsing import Word, alphas
+
+ # define grammar of a greeting
+ greet = Word(alphas) + "," + Word(alphas) + "!"
+
+ hello = "Hello, World!"
+ print(hello, "->", greet.parse_string(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 :class:`'+'<And>`,
+:class:`'|'<MatchFirst>`, :class:`'^'<Or>` and :class:`'&'<Each>` operators.
+
+The :class:`ParseResults` object returned from
+:class:`ParserElement.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
+
+
+Getting Started -
+-----------------
+Visit the classes :class:`ParserElement` and :class:`ParseResults` to
+see the base classes that most other pyparsing
+classes inherit from. Use the docstrings for examples of how to:
+
+ - construct literal match expressions from :class:`Literal` and
+ :class:`CaselessLiteral` classes
+ - construct character word-group expressions using the :class:`Word`
+ class
+ - see how to create repetitive expressions using :class:`ZeroOrMore`
+ and :class:`OneOrMore` classes
+ - use :class:`'+'<And>`, :class:`'|'<MatchFirst>`, :class:`'^'<Or>`,
+ and :class:`'&'<Each>` operators to combine simple expressions into
+ more complex ones
+ - associate names with your parsed results using
+ :class:`ParserElement.setResultsName`
+ - access the parsed data, which is returned as a :class:`ParseResults`
+ object
+ - find some helpful expression short-cuts like :class:`delimitedList`
+ and :class:`oneOf`
+ - find more useful common expressions in the :class:`pyparsing_common`
+ namespace class
+"""
+from typing import NamedTuple
+
+
+class version_info(NamedTuple):
+ major: int
+ minor: int
+ micro: int
+ releaselevel: str
+ serial: int
+
+ @property
+ def __version__(self):
+ return (
+ "{}.{}.{}".format(self.major, self.minor, self.micro)
+ + (
+ "{}{}{}".format(
+ "r" if self.releaselevel[0] == "c" else "",
+ self.releaselevel[0],
+ self.serial,
+ ),
+ "",
+ )[self.releaselevel == "final"]
+ )
+
+ def __str__(self):
+ return "{} {} / {}".format(__name__, self.__version__, __version_time__)
+
+ def __repr__(self):
+ return "{}.{}({})".format(
+ __name__,
+ type(self).__name__,
+ ", ".join("{}={!r}".format(*nv) for nv in zip(self._fields, self)),
+ )
+
+
+__version_info__ = version_info(3, 0, 9, "final", 0)
+__version_time__ = "05 May 2022 07:02 UTC"
+__version__ = __version_info__.__version__
+__versionTime__ = __version_time__
+__author__ = "Paul McGuire <ptmcg.gm+pyparsing@gmail.com>"
+
+from .util import *
+from .exceptions import *
+from .actions import *
+from .core import __diag__, __compat__
+from .results import *
+from .core import *
+from .core import _builtin_exprs as core_builtin_exprs
+from .helpers import *
+from .helpers import _builtin_exprs as helper_builtin_exprs
+
+from .unicode import unicode_set, UnicodeRangeList, pyparsing_unicode as unicode
+from .testing import pyparsing_test as testing
+from .common import (
+ pyparsing_common as common,
+ _builtin_exprs as common_builtin_exprs,
+)
+
+# define backward compat synonyms
+if "pyparsing_unicode" not in globals():
+ pyparsing_unicode = unicode
+if "pyparsing_common" not in globals():
+ pyparsing_common = common
+if "pyparsing_test" not in globals():
+ pyparsing_test = testing
+
+core_builtin_exprs += common_builtin_exprs + helper_builtin_exprs
+
+
+__all__ = [
+ "__version__",
+ "__version_time__",
+ "__author__",
+ "__compat__",
+ "__diag__",
+ "And",
+ "AtLineStart",
+ "AtStringStart",
+ "CaselessKeyword",
+ "CaselessLiteral",
+ "CharsNotIn",
+ "Combine",
+ "Dict",
+ "Each",
+ "Empty",
+ "FollowedBy",
+ "Forward",
+ "GoToColumn",
+ "Group",
+ "IndentedBlock",
+ "Keyword",
+ "LineEnd",
+ "LineStart",
+ "Literal",
+ "Located",
+ "PrecededBy",
+ "MatchFirst",
+ "NoMatch",
+ "NotAny",
+ "OneOrMore",
+ "OnlyOnce",
+ "OpAssoc",
+ "Opt",
+ "Optional",
+ "Or",
+ "ParseBaseException",
+ "ParseElementEnhance",
+ "ParseException",
+ "ParseExpression",
+ "ParseFatalException",
+ "ParseResults",
+ "ParseSyntaxException",
+ "ParserElement",
+ "PositionToken",
+ "QuotedString",
+ "RecursiveGrammarException",
+ "Regex",
+ "SkipTo",
+ "StringEnd",
+ "StringStart",
+ "Suppress",
+ "Token",
+ "TokenConverter",
+ "White",
+ "Word",
+ "WordEnd",
+ "WordStart",
+ "ZeroOrMore",
+ "Char",
+ "alphanums",
+ "alphas",
+ "alphas8bit",
+ "any_close_tag",
+ "any_open_tag",
+ "c_style_comment",
+ "col",
+ "common_html_entity",
+ "counted_array",
+ "cpp_style_comment",
+ "dbl_quoted_string",
+ "dbl_slash_comment",
+ "delimited_list",
+ "dict_of",
+ "empty",
+ "hexnums",
+ "html_comment",
+ "identchars",
+ "identbodychars",
+ "java_style_comment",
+ "line",
+ "line_end",
+ "line_start",
+ "lineno",
+ "make_html_tags",
+ "make_xml_tags",
+ "match_only_at_col",
+ "match_previous_expr",
+ "match_previous_literal",
+ "nested_expr",
+ "null_debug_action",
+ "nums",
+ "one_of",
+ "printables",
+ "punc8bit",
+ "python_style_comment",
+ "quoted_string",
+ "remove_quotes",
+ "replace_with",
+ "replace_html_entity",
+ "rest_of_line",
+ "sgl_quoted_string",
+ "srange",
+ "string_end",
+ "string_start",
+ "trace_parse_action",
+ "unicode_string",
+ "with_attribute",
+ "indentedBlock",
+ "original_text_for",
+ "ungroup",
+ "infix_notation",
+ "locatedExpr",
+ "with_class",
+ "CloseMatch",
+ "token_map",
+ "pyparsing_common",
+ "pyparsing_unicode",
+ "unicode_set",
+ "condition_as_parse_action",
+ "pyparsing_test",
+ # pre-PEP8 compatibility names
+ "__versionTime__",
+ "anyCloseTag",
+ "anyOpenTag",
+ "cStyleComment",
+ "commonHTMLEntity",
+ "countedArray",
+ "cppStyleComment",
+ "dblQuotedString",
+ "dblSlashComment",
+ "delimitedList",
+ "dictOf",
+ "htmlComment",
+ "javaStyleComment",
+ "lineEnd",
+ "lineStart",
+ "makeHTMLTags",
+ "makeXMLTags",
+ "matchOnlyAtCol",
+ "matchPreviousExpr",
+ "matchPreviousLiteral",
+ "nestedExpr",
+ "nullDebugAction",
+ "oneOf",
+ "opAssoc",
+ "pythonStyleComment",
+ "quotedString",
+ "removeQuotes",
+ "replaceHTMLEntity",
+ "replaceWith",
+ "restOfLine",
+ "sglQuotedString",
+ "stringEnd",
+ "stringStart",
+ "traceParseAction",
+ "unicodeString",
+ "withAttribute",
+ "indentedBlock",
+ "originalTextFor",
+ "infixNotation",
+ "locatedExpr",
+ "withClass",
+ "tokenMap",
+ "conditionAsParseAction",
+ "autoname_elements",
+]
diff --git a/src/pip/_vendor/pyparsing/actions.py b/src/pip/_vendor/pyparsing/actions.py
new file mode 100644
index 000000000..f72c66e74
--- /dev/null
+++ b/src/pip/_vendor/pyparsing/actions.py
@@ -0,0 +1,207 @@
+# actions.py
+
+from .exceptions import ParseException
+from .util import col
+
+
+class OnlyOnce:
+ """
+ Wrapper for parse actions, to ensure they are only called once.
+ """
+
+ def __init__(self, method_call):
+ from .core import _trim_arity
+
+ self.callable = _trim_arity(method_call)
+ self.called = False
+
+ def __call__(self, s, l, t):
+ if not self.called:
+ results = self.callable(s, l, t)
+ self.called = True
+ return results
+ raise ParseException(s, l, "OnlyOnce obj called multiple times w/out reset")
+
+ def reset(self):
+ """
+ Allow the associated parse action to be called once more.
+ """
+
+ self.called = False
+
+
+def match_only_at_col(n):
+ """
+ Helper method for defining parse actions that require matching at
+ a specific column in the input text.
+ """
+
+ def verify_col(strg, locn, toks):
+ if col(locn, strg) != n:
+ raise ParseException(strg, locn, "matched token not at column {}".format(n))
+
+ return verify_col
+
+
+def replace_with(repl_str):
+ """
+ Helper method for common parse actions that simply return
+ a literal value. Especially useful when used with
+ :class:`transform_string<ParserElement.transform_string>` ().
+
+ Example::
+
+ num = Word(nums).set_parse_action(lambda toks: int(toks[0]))
+ na = one_of("N/A NA").set_parse_action(replace_with(math.nan))
+ term = na | num
+
+ term[1, ...].parse_string("324 234 N/A 234") # -> [324, 234, nan, 234]
+ """
+ return lambda s, l, t: [repl_str]
+
+
+def remove_quotes(s, l, t):
+ """
+ Helper parse action for removing quotation marks from parsed
+ quoted strings.
+
+ Example::
+
+ # by default, quotation marks are included in parsed results
+ quoted_string.parse_string("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"]
+
+ # use remove_quotes to strip quotation marks from parsed results
+ quoted_string.set_parse_action(remove_quotes)
+ quoted_string.parse_string("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"]
+ """
+ return t[0][1:-1]
+
+
+def with_attribute(*args, **attr_dict):
+ """
+ Helper to create a validating parse action to be used with start
+ tags created with :class:`make_xml_tags` or
+ :class:`make_html_tags`. Use ``with_attribute`` to qualify
+ a starting tag with a required attribute value, to avoid false
+ matches on common tags such as ``<TD>`` or ``<DIV>``.
+
+ Call ``with_attribute`` with a series of attribute names and
+ values. Specify the list of filter attributes names and values as:
+
+ - keyword arguments, as in ``(align="right")``, or
+ - as an explicit dict with ``**`` operator, when an attribute
+ name is also a Python reserved word, as in ``**{"class":"Customer", "align":"right"}``
+ - a list of name-value tuples, as in ``(("ns1:class", "Customer"), ("ns2:align", "right"))``
+
+ For attribute names with a namespace prefix, you must use the second
+ form. Attribute names are matched insensitive to upper/lower case.
+
+ If just testing for ``class`` (with or without a namespace), use
+ :class:`with_class`.
+
+ To verify that the attribute exists, but without specifying a value,
+ pass ``with_attribute.ANY_VALUE`` as the value.
+
+ Example::
+
+ html = '''
+ <div>
+ Some text
+ <div type="grid">1 4 0 1 0</div>
+ <div type="graph">1,3 2,3 1,1</div>
+ <div>this has no type</div>
+ </div>
+
+ '''
+ div,div_end = make_html_tags("div")
+
+ # only match div tag having a type attribute with value "grid"
+ div_grid = div().set_parse_action(with_attribute(type="grid"))
+ grid_expr = div_grid + SkipTo(div | div_end)("body")
+ for grid_header in grid_expr.search_string(html):
+ print(grid_header.body)
+
+ # construct a match with any div tag having a type attribute, regardless of the value
+ div_any_type = div().set_parse_action(with_attribute(type=with_attribute.ANY_VALUE))
+ div_expr = div_any_type + SkipTo(div | div_end)("body")
+ for div_header in div_expr.search_string(html):
+ print(div_header.body)
+
+ prints::
+
+ 1 4 0 1 0
+
+ 1 4 0 1 0
+ 1,3 2,3 1,1
+ """
+ if args:
+ attrs = args[:]
+ else:
+ attrs = attr_dict.items()
+ attrs = [(k, v) for k, v in attrs]
+
+ def pa(s, l, tokens):
+ for attrName, attrValue in attrs:
+ if attrName not in tokens:
+ raise ParseException(s, l, "no matching attribute " + attrName)
+ if attrValue != with_attribute.ANY_VALUE and tokens[attrName] != attrValue:
+ raise ParseException(
+ s,
+ l,
+ "attribute {!r} has value {!r}, must be {!r}".format(
+ attrName, tokens[attrName], attrValue
+ ),
+ )
+
+ return pa
+
+
+with_attribute.ANY_VALUE = object()
+
+
+def with_class(classname, namespace=""):
+ """
+ Simplified version of :class:`with_attribute` when
+ matching on a div class - made difficult because ``class`` is
+ a reserved word in Python.
+
+ Example::
+
+ html = '''
+ <div>
+ Some text
+ <div class="grid">1 4 0 1 0</div>
+ <div class="graph">1,3 2,3 1,1</div>
+ <div>this &lt;div&gt; has no class</div>
+ </div>
+
+ '''
+ div,div_end = make_html_tags("div")
+ div_grid = div().set_parse_action(with_class("grid"))
+
+ grid_expr = div_grid + SkipTo(div | div_end)("body")
+ for grid_header in grid_expr.search_string(html):
+ print(grid_header.body)
+
+ div_any_type = div().set_parse_action(with_class(withAttribute.ANY_VALUE))
+ div_expr = div_any_type + SkipTo(div | div_end)("body")
+ for div_header in div_expr.search_string(html):
+ print(div_header.body)
+
+ prints::
+
+ 1 4 0 1 0
+
+ 1 4 0 1 0
+ 1,3 2,3 1,1
+ """
+ classattr = "{}:class".format(namespace) if namespace else "class"
+ return with_attribute(**{classattr: classname})
+
+
+# pre-PEP8 compatibility symbols
+replaceWith = replace_with
+removeQuotes = remove_quotes
+withAttribute = with_attribute
+withClass = with_class
+matchOnlyAtCol = match_only_at_col
diff --git a/src/pip/_vendor/pyparsing/common.py b/src/pip/_vendor/pyparsing/common.py
new file mode 100644
index 000000000..1859fb79c
--- /dev/null
+++ b/src/pip/_vendor/pyparsing/common.py
@@ -0,0 +1,424 @@
+# common.py
+from .core import *
+from .helpers import delimited_list, any_open_tag, any_close_tag
+from datetime import datetime
+
+
+# some other useful expressions - using lower-case class name since we are really using this as a namespace
+class pyparsing_common:
+ """Here are some common low-level expressions that may be useful in
+ jump-starting parser development:
+
+ - numeric forms (:class:`integers<integer>`, :class:`reals<real>`,
+ :class:`scientific notation<sci_real>`)
+ - common :class:`programming identifiers<identifier>`
+ - network addresses (:class:`MAC<mac_address>`,
+ :class:`IPv4<ipv4_address>`, :class:`IPv6<ipv6_address>`)
+ - ISO8601 :class:`dates<iso8601_date>` and
+ :class:`datetime<iso8601_datetime>`
+ - :class:`UUID<uuid>`
+ - :class:`comma-separated list<comma_separated_list>`
+ - :class:`url`
+
+ Parse actions:
+
+ - :class:`convertToInteger`
+ - :class:`convertToFloat`
+ - :class:`convertToDate`
+ - :class:`convertToDatetime`
+ - :class:`stripHTMLTags`
+ - :class:`upcaseTokens`
+ - :class:`downcaseTokens`
+
+ Example::
+
+ pyparsing_common.number.runTests('''
+ # any int or real number, returned as the appropriate type
+ 100
+ -100
+ +100
+ 3.14159
+ 6.02e23
+ 1e-12
+ ''')
+
+ pyparsing_common.fnumber.runTests('''
+ # any int or real number, returned as float
+ 100
+ -100
+ +100
+ 3.14159
+ 6.02e23
+ 1e-12
+ ''')
+
+ pyparsing_common.hex_integer.runTests('''
+ # hex numbers
+ 100
+ FF
+ ''')
+
+ pyparsing_common.fraction.runTests('''
+ # fractions
+ 1/2
+ -3/4
+ ''')
+
+ pyparsing_common.mixed_integer.runTests('''
+ # mixed fractions
+ 1
+ 1/2
+ -3/4
+ 1-3/4
+ ''')
+
+ import uuid
+ pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID))
+ pyparsing_common.uuid.runTests('''
+ # uuid
+ 12345678-1234-5678-1234-567812345678
+ ''')
+
+ prints::
+
+ # any int or real number, returned as the appropriate type
+ 100
+ [100]
+
+ -100
+ [-100]
+
+ +100
+ [100]
+
+ 3.14159
+ [3.14159]
+
+ 6.02e23
+ [6.02e+23]
+
+ 1e-12
+ [1e-12]
+
+ # any int or real number, returned as float
+ 100
+ [100.0]
+
+ -100
+ [-100.0]
+
+ +100
+ [100.0]
+
+ 3.14159
+ [3.14159]
+
+ 6.02e23
+ [6.02e+23]
+
+ 1e-12
+ [1e-12]
+
+ # hex numbers
+ 100
+ [256]
+
+ FF
+ [255]
+
+ # fractions
+ 1/2
+ [0.5]
+
+ -3/4
+ [-0.75]
+
+ # mixed fractions
+ 1
+ [1]
+
+ 1/2
+ [0.5]
+
+ -3/4
+ [-0.75]
+
+ 1-3/4
+ [1.75]
+
+ # uuid
+ 12345678-1234-5678-1234-567812345678
+ [UUID('12345678-1234-5678-1234-567812345678')]
+ """
+
+ convert_to_integer = token_map(int)
+ """
+ Parse action for converting parsed integers to Python int
+ """
+
+ convert_to_float = token_map(float)
+ """
+ Parse action for converting parsed numbers to Python float
+ """
+
+ integer = Word(nums).set_name("integer").set_parse_action(convert_to_integer)
+ """expression that parses an unsigned integer, returns an int"""
+
+ hex_integer = (
+ Word(hexnums).set_name("hex integer").set_parse_action(token_map(int, 16))
+ )
+ """expression that parses a hexadecimal integer, returns an int"""
+
+ signed_integer = (
+ Regex(r"[+-]?\d+")
+ .set_name("signed integer")
+ .set_parse_action(convert_to_integer)
+ )
+ """expression that parses an integer with optional leading sign, returns an int"""
+
+ fraction = (
+ signed_integer().set_parse_action(convert_to_float)
+ + "/"
+ + signed_integer().set_parse_action(convert_to_float)
+ ).set_name("fraction")
+ """fractional expression of an integer divided by an integer, returns a float"""
+ fraction.add_parse_action(lambda tt: tt[0] / tt[-1])
+
+ mixed_integer = (
+ fraction | signed_integer + Opt(Opt("-").suppress() + fraction)
+ ).set_name("fraction or mixed integer-fraction")
+ """mixed integer of the form 'integer - fraction', with optional leading integer, returns float"""
+ mixed_integer.add_parse_action(sum)
+
+ real = (
+ Regex(r"[+-]?(?:\d+\.\d*|\.\d+)")
+ .set_name("real number")
+ .set_parse_action(convert_to_float)
+ )
+ """expression that parses a floating point number and returns a float"""
+
+ sci_real = (
+ Regex(r"[+-]?(?:\d+(?:[eE][+-]?\d+)|(?:\d+\.\d*|\.\d+)(?:[eE][+-]?\d+)?)")
+ .set_name("real number with scientific notation")
+ .set_parse_action(convert_to_float)
+ )
+ """expression that parses a floating point number with optional
+ scientific notation and returns a float"""
+
+ # streamlining this expression makes the docs nicer-looking
+ number = (sci_real | real | signed_integer).setName("number").streamline()
+ """any numeric expression, returns the corresponding Python type"""
+
+ fnumber = (
+ Regex(r"[+-]?\d+\.?\d*([eE][+-]?\d+)?")
+ .set_name("fnumber")
+ .set_parse_action(convert_to_float)
+ )
+ """any int or real number, returned as float"""
+
+ identifier = Word(identchars, identbodychars).set_name("identifier")
+ """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')"""
+
+ ipv4_address = Regex(
+ r"(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}"
+ ).set_name("IPv4 address")
+ "IPv4 address (``0.0.0.0 - 255.255.255.255``)"
+
+ _ipv6_part = Regex(r"[0-9a-fA-F]{1,4}").set_name("hex_integer")
+ _full_ipv6_address = (_ipv6_part + (":" + _ipv6_part) * 7).set_name(
+ "full IPv6 address"
+ )
+ _short_ipv6_address = (
+ Opt(_ipv6_part + (":" + _ipv6_part) * (0, 6))
+ + "::"
+ + Opt(_ipv6_part + (":" + _ipv6_part) * (0, 6))
+ ).set_name("short IPv6 address")
+ _short_ipv6_address.add_condition(
+ lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8
+ )
+ _mixed_ipv6_address = ("::ffff:" + ipv4_address).set_name("mixed IPv6 address")
+ ipv6_address = Combine(
+ (_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).set_name(
+ "IPv6 address"
+ )
+ ).set_name("IPv6 address")
+ "IPv6 address (long, short, or mixed form)"
+
+ mac_address = Regex(
+ r"[0-9a-fA-F]{2}([:.-])[0-9a-fA-F]{2}(?:\1[0-9a-fA-F]{2}){4}"
+ ).set_name("MAC address")
+ "MAC address xx:xx:xx:xx:xx (may also have '-' or '.' delimiters)"
+
+ @staticmethod
+ def convert_to_date(fmt: str = "%Y-%m-%d"):
+ """
+ Helper to create a parse action for converting parsed date string to Python datetime.date
+
+ Params -
+ - fmt - format to be passed to datetime.strptime (default= ``"%Y-%m-%d"``)
+
+ Example::
+
+ date_expr = pyparsing_common.iso8601_date.copy()
+ date_expr.setParseAction(pyparsing_common.convertToDate())
+ print(date_expr.parseString("1999-12-31"))
+
+ prints::
+
+ [datetime.date(1999, 12, 31)]
+ """
+
+ def cvt_fn(ss, ll, tt):
+ try:
+ return datetime.strptime(tt[0], fmt).date()
+ except ValueError as ve:
+ raise ParseException(ss, ll, str(ve))
+
+ return cvt_fn
+
+ @staticmethod
+ def convert_to_datetime(fmt: str = "%Y-%m-%dT%H:%M:%S.%f"):
+ """Helper to create a parse action for converting parsed
+ datetime string to Python datetime.datetime
+
+ Params -
+ - fmt - format to be passed to datetime.strptime (default= ``"%Y-%m-%dT%H:%M:%S.%f"``)
+
+ Example::
+
+ dt_expr = pyparsing_common.iso8601_datetime.copy()
+ dt_expr.setParseAction(pyparsing_common.convertToDatetime())
+ print(dt_expr.parseString("1999-12-31T23:59:59.999"))
+
+ prints::
+
+ [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)]
+ """
+
+ def cvt_fn(s, l, t):
+ try:
+ return datetime.strptime(t[0], fmt)
+ except ValueError as ve:
+ raise ParseException(s, l, str(ve))
+
+ return cvt_fn
+
+ iso8601_date = Regex(
+ r"(?P<year>\d{4})(?:-(?P<month>\d\d)(?:-(?P<day>\d\d))?)?"
+ ).set_name("ISO8601 date")
+ "ISO8601 date (``yyyy-mm-dd``)"
+
+ iso8601_datetime = Regex(
+ r"(?P<year>\d{4})-(?P<month>\d\d)-(?P<day>\d\d)[T ](?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d(\.\d*)?)?)?(?P<tz>Z|[+-]\d\d:?\d\d)?"
+ ).set_name("ISO8601 datetime")
+ "ISO8601 datetime (``yyyy-mm-ddThh:mm:ss.s(Z|+-00:00)``) - trailing seconds, milliseconds, and timezone optional; accepts separating ``'T'`` or ``' '``"
+
+ uuid = Regex(r"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}").set_name("UUID")
+ "UUID (``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx``)"
+
+ _html_stripper = any_open_tag.suppress() | any_close_tag.suppress()
+
+ @staticmethod
+ def strip_html_tags(s: str, l: int, tokens: ParseResults):
+ """Parse action to remove HTML tags from web page HTML source
+
+ Example::
+
+ # strip HTML links from normal text
+ text = '<td>More info at the <a href="https://github.com/pyparsing/pyparsing/wiki">pyparsing</a> wiki page</td>'
+ td, td_end = makeHTMLTags("TD")
+ table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end
+ print(table_text.parseString(text).body)
+
+ Prints::
+
+ More info at the pyparsing wiki page
+ """
+ return pyparsing_common._html_stripper.transform_string(tokens[0])
+
+ _commasepitem = (
+ Combine(
+ OneOrMore(
+ ~Literal(",")
+ + ~LineEnd()
+ + Word(printables, exclude_chars=",")
+ + Opt(White(" \t") + ~FollowedBy(LineEnd() | ","))
+ )
+ )
+ .streamline()
+ .set_name("commaItem")
+ )
+ comma_separated_list = delimited_list(
+ Opt(quoted_string.copy() | _commasepitem, default="")
+ ).set_name("comma separated list")
+ """Predefined expression of 1 or more printable words or quoted strings, separated by commas."""
+
+ upcase_tokens = staticmethod(token_map(lambda t: t.upper()))
+ """Parse action to convert tokens to upper case."""
+
+ downcase_tokens = staticmethod(token_map(lambda t: t.lower()))
+ """Parse action to convert tokens to lower case."""
+
+ # fmt: off
+ url = Regex(
+ # https://mathiasbynens.be/demo/url-regex
+ # https://gist.github.com/dperini/729294
+ r"^" +
+ # protocol identifier (optional)
+ # short syntax // still required
+ r"(?:(?:(?P<scheme>https?|ftp):)?\/\/)" +
+ # user:pass BasicAuth (optional)
+ r"(?:(?P<auth>\S+(?::\S*)?)@)?" +
+ r"(?P<host>" +
+ # IP address exclusion
+ # private & local networks
+ r"(?!(?:10|127)(?:\.\d{1,3}){3})" +
+ r"(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})" +
+ r"(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})" +
+ # IP address dotted notation octets
+ # excludes loopback network 0.0.0.0
+ # excludes reserved space >= 224.0.0.0
+ # excludes network & broadcast addresses
+ # (first & last IP address of each class)
+ r"(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])" +
+ r"(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}" +
+ r"(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))" +
+ r"|" +
+ # host & domain names, may end with dot
+ # can be replaced by a shortest alternative
+ # (?![-_])(?:[-\w\u00a1-\uffff]{0,63}[^-_]\.)+
+ r"(?:" +
+ r"(?:" +
+ r"[a-z0-9\u00a1-\uffff]" +
+ r"[a-z0-9\u00a1-\uffff_-]{0,62}" +
+ r")?" +
+ r"[a-z0-9\u00a1-\uffff]\." +
+ r")+" +
+ # TLD identifier name, may end with dot
+ r"(?:[a-z\u00a1-\uffff]{2,}\.?)" +
+ r")" +
+ # port number (optional)
+ r"(:(?P<port>\d{2,5}))?" +
+ # resource path (optional)
+ r"(?P<path>\/[^?# ]*)?" +
+ # query string (optional)
+ r"(\?(?P<query>[^#]*))?" +
+ # fragment (optional)
+ r"(#(?P<fragment>\S*))?" +
+ r"$"
+ ).set_name("url")
+ # fmt: on
+
+ # pre-PEP8 compatibility names
+ convertToInteger = convert_to_integer
+ convertToFloat = convert_to_float
+ convertToDate = convert_to_date
+ convertToDatetime = convert_to_datetime
+ stripHTMLTags = strip_html_tags
+ upcaseTokens = upcase_tokens
+ downcaseTokens = downcase_tokens
+
+
+_builtin_exprs = [
+ v for v in vars(pyparsing_common).values() if isinstance(v, ParserElement)
+]
diff --git a/src/pip/_vendor/pyparsing/core.py b/src/pip/_vendor/pyparsing/core.py
new file mode 100644
index 000000000..6ff3c766f
--- /dev/null
+++ b/src/pip/_vendor/pyparsing/core.py
@@ -0,0 +1,5814 @@
+#
+# core.py
+#
+import os
+import typing
+from typing import (
+ NamedTuple,
+ Union,
+ Callable,
+ Any,
+ Generator,
+ Tuple,
+ List,
+ TextIO,
+ Set,
+ Sequence,
+)
+from abc import ABC, abstractmethod
+from enum import Enum
+import string
+import copy
+import warnings
+import re
+import sys
+from collections.abc import Iterable
+import traceback
+import types
+from operator import itemgetter
+from functools import wraps
+from threading import RLock
+from pathlib import Path
+
+from .util import (
+ _FifoCache,
+ _UnboundedCache,
+ __config_flags,
+ _collapse_string_to_ranges,
+ _escape_regex_range_chars,
+ _bslash,
+ _flatten,
+ LRUMemo as _LRUMemo,
+ UnboundedMemo as _UnboundedMemo,
+)
+from .exceptions import *
+from .actions import *
+from .results import ParseResults, _ParseResultsWithOffset
+from .unicode import pyparsing_unicode
+
+_MAX_INT = sys.maxsize
+str_type: Tuple[type, ...] = (str, bytes)
+
+#
+# Copyright (c) 2003-2022 Paul T. McGuire
+#
+# 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.
+#
+
+
+if sys.version_info >= (3, 8):
+ from functools import cached_property
+else:
+
+ class cached_property:
+ def __init__(self, func):
+ self._func = func
+
+ def __get__(self, instance, owner=None):
+ ret = instance.__dict__[self._func.__name__] = self._func(instance)
+ return ret
+
+
+class __compat__(__config_flags):
+ """
+ A cross-version compatibility configuration for pyparsing features that will be
+ released in a future version. By setting values in this configuration to True,
+ those features can be enabled in prior versions for compatibility development
+ and testing.
+
+ - ``collect_all_And_tokens`` - flag to enable fix for Issue #63 that fixes erroneous grouping
+ of results names when an :class:`And` expression is nested within an :class:`Or` or :class:`MatchFirst`;
+ maintained for compatibility, but setting to ``False`` no longer restores pre-2.3.1
+ behavior
+ """
+
+ _type_desc = "compatibility"
+
+ collect_all_And_tokens = True
+
+ _all_names = [__ for __ in locals() if not __.startswith("_")]
+ _fixed_names = """
+ collect_all_And_tokens
+ """.split()
+
+
+class __diag__(__config_flags):
+ _type_desc = "diagnostic"
+
+ warn_multiple_tokens_in_named_alternation = False
+ warn_ungrouped_named_tokens_in_collection = False
+ warn_name_set_on_empty_Forward = False
+ warn_on_parse_using_empty_Forward = False
+ warn_on_assignment_to_Forward = False
+ warn_on_multiple_string_args_to_oneof = False
+ warn_on_match_first_with_lshift_operator = False
+ enable_debug_on_named_expressions = False
+
+ _all_names = [__ for __ in locals() if not __.startswith("_")]
+ _warning_names = [name for name in _all_names if name.startswith("warn")]
+ _debug_names = [name for name in _all_names if name.startswith("enable_debug")]
+
+ @classmethod
+ def enable_all_warnings(cls) -> None:
+ for name in cls._warning_names:
+ cls.enable(name)
+
+
+class Diagnostics(Enum):
+ """
+ Diagnostic configuration (all default to disabled)
+ - ``warn_multiple_tokens_in_named_alternation`` - flag to enable warnings when a results
+ name is defined on a :class:`MatchFirst` or :class:`Or` expression with one or more :class:`And` subexpressions
+ - ``warn_ungrouped_named_tokens_in_collection`` - flag to enable warnings when a results
+ name is defined on a containing expression with ungrouped subexpressions that also
+ have results names
+ - ``warn_name_set_on_empty_Forward`` - flag to enable warnings when a :class:`Forward` is defined
+ with a results name, but has no contents defined
+ - ``warn_on_parse_using_empty_Forward`` - flag to enable warnings when a :class:`Forward` is
+ defined in a grammar but has never had an expression attached to it
+ - ``warn_on_assignment_to_Forward`` - flag to enable warnings when a :class:`Forward` is defined
+ but is overwritten by assigning using ``'='`` instead of ``'<<='`` or ``'<<'``
+ - ``warn_on_multiple_string_args_to_oneof`` - flag to enable warnings when :class:`one_of` is
+ incorrectly called with multiple str arguments
+ - ``enable_debug_on_named_expressions`` - flag to auto-enable debug on all subsequent
+ calls to :class:`ParserElement.set_name`
+
+ Diagnostics are enabled/disabled by calling :class:`enable_diag` and :class:`disable_diag`.
+ All warnings can be enabled by calling :class:`enable_all_warnings`.
+ """
+
+ warn_multiple_tokens_in_named_alternation = 0
+ warn_ungrouped_named_tokens_in_collection = 1
+ warn_name_set_on_empty_Forward = 2
+ warn_on_parse_using_empty_Forward = 3
+ warn_on_assignment_to_Forward = 4
+ warn_on_multiple_string_args_to_oneof = 5
+ warn_on_match_first_with_lshift_operator = 6
+ enable_debug_on_named_expressions = 7
+
+
+def enable_diag(diag_enum: Diagnostics) -> None:
+ """
+ Enable a global pyparsing diagnostic flag (see :class:`Diagnostics`).
+ """
+ __diag__.enable(diag_enum.name)
+
+
+def disable_diag(diag_enum: Diagnostics) -> None:
+ """
+ Disable a global pyparsing diagnostic flag (see :class:`Diagnostics`).
+ """
+ __diag__.disable(diag_enum.name)
+
+
+def enable_all_warnings() -> None:
+ """
+ Enable all global pyparsing diagnostic warnings (see :class:`Diagnostics`).
+ """
+ __diag__.enable_all_warnings()
+
+
+# hide abstract class
+del __config_flags
+
+
+def _should_enable_warnings(
+ cmd_line_warn_options: typing.Iterable[str], warn_env_var: typing.Optional[str]
+) -> bool:
+ enable = bool(warn_env_var)
+ for warn_opt in cmd_line_warn_options:
+ w_action, w_message, w_category, w_module, w_line = (warn_opt + "::::").split(
+ ":"
+ )[:5]
+ if not w_action.lower().startswith("i") and (
+ not (w_message or w_category or w_module) or w_module == "pyparsing"
+ ):
+ enable = True
+ elif w_action.lower().startswith("i") and w_module in ("pyparsing", ""):
+ enable = False
+ return enable
+
+
+if _should_enable_warnings(
+ sys.warnoptions, os.environ.get("PYPARSINGENABLEALLWARNINGS")
+):
+ enable_all_warnings()
+
+
+# build list of single arg builtins, that can be used as parse actions
+_single_arg_builtins = {
+ sum,
+ len,
+ sorted,
+ reversed,
+ list,
+ tuple,
+ set,
+ any,
+ all,
+ min,
+ max,
+}
+
+_generatorType = types.GeneratorType
+ParseAction = Union[
+ Callable[[], Any],
+ Callable[[ParseResults], Any],
+ Callable[[int, ParseResults], Any],
+ Callable[[str, int, ParseResults], Any],
+]
+ParseCondition = Union[
+ Callable[[], bool],
+ Callable[[ParseResults], bool],
+ Callable[[int, ParseResults], bool],
+ Callable[[str, int, ParseResults], bool],
+]
+ParseFailAction = Callable[[str, int, "ParserElement", Exception], None]
+DebugStartAction = Callable[[str, int, "ParserElement", bool], None]
+DebugSuccessAction = Callable[
+ [str, int, int, "ParserElement", ParseResults, bool], None
+]
+DebugExceptionAction = Callable[[str, int, "ParserElement", Exception, bool], None]
+
+
+alphas = string.ascii_uppercase + string.ascii_lowercase
+identchars = pyparsing_unicode.Latin1.identchars
+identbodychars = pyparsing_unicode.Latin1.identbodychars
+nums = "0123456789"
+hexnums = nums + "ABCDEFabcdef"
+alphanums = alphas + nums
+printables = "".join([c for c in string.printable if c not in string.whitespace])
+
+_trim_arity_call_line: traceback.StackSummary = None
+
+
+def _trim_arity(func, max_limit=3):
+ """decorator to trim function calls to match the arity of the target"""
+ global _trim_arity_call_line
+
+ if func in _single_arg_builtins:
+ return lambda s, l, t: func(t)
+
+ limit = 0
+ found_arity = False
+
+ def extract_tb(tb, limit=0):
+ frames = traceback.extract_tb(tb, limit=limit)
+ frame_summary = frames[-1]
+ return [frame_summary[:2]]
+
+ # synthesize what would be returned by traceback.extract_stack at the call to
+ # user's parse action 'func', so that we don't incur call penalty at parse time
+
+ # fmt: off
+ LINE_DIFF = 7
+ # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND
+ # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!!
+ _trim_arity_call_line = (_trim_arity_call_line or traceback.extract_stack(limit=2)[-1])
+ pa_call_line_synth = (_trim_arity_call_line[0], _trim_arity_call_line[1] + LINE_DIFF)
+
+ def wrapper(*args):
+ nonlocal found_arity, limit
+ while 1:
+ try:
+ ret = func(*args[limit:])
+ found_arity = True
+ return ret
+ except TypeError as te:
+ # re-raise TypeErrors if they did not come from our arity testing
+ if found_arity:
+ raise
+ else:
+ tb = te.__traceback__
+ trim_arity_type_error = (
+ extract_tb(tb, limit=2)[-1][:2] == pa_call_line_synth
+ )
+ del tb
+
+ if trim_arity_type_error:
+ if limit < max_limit:
+ limit += 1
+ continue
+
+ raise
+ # fmt: on
+
+ # copy func name to wrapper for sensible debug output
+ # (can't use functools.wraps, since that messes with function signature)
+ func_name = getattr(func, "__name__", getattr(func, "__class__").__name__)
+ wrapper.__name__ = func_name
+ wrapper.__doc__ = func.__doc__
+
+ return wrapper
+
+
+def condition_as_parse_action(
+ fn: ParseCondition, message: str = None, fatal: bool = False
+) -> ParseAction:
+ """
+ Function to convert a simple predicate function that returns ``True`` or ``False``
+ into a parse action. Can be used in places when a parse action is required
+ and :class:`ParserElement.add_condition` cannot be used (such as when adding a condition
+ to an operator level in :class:`infix_notation`).
+
+ Optional keyword arguments:
+
+ - ``message`` - define a custom message to be used in the raised exception
+ - ``fatal`` - if True, will raise :class:`ParseFatalException` to stop parsing immediately;
+ otherwise will raise :class:`ParseException`
+
+ """
+ msg = message if message is not None else "failed user-defined condition"
+ exc_type = ParseFatalException if fatal else ParseException
+ fn = _trim_arity(fn)
+
+ @wraps(fn)
+ def pa(s, l, t):
+ if not bool(fn(s, l, t)):
+ raise exc_type(s, l, msg)
+
+ return pa
+
+
+def _default_start_debug_action(
+ instring: str, loc: int, expr: "ParserElement", cache_hit: bool = False
+):
+ cache_hit_str = "*" if cache_hit else ""
+ print(
+ (
+ "{}Match {} at loc {}({},{})\n {}\n {}^".format(
+ cache_hit_str,
+ expr,
+ loc,
+ lineno(loc, instring),
+ col(loc, instring),
+ line(loc, instring),
+ " " * (col(loc, instring) - 1),
+ )
+ )
+ )
+
+
+def _default_success_debug_action(
+ instring: str,
+ startloc: int,
+ endloc: int,
+ expr: "ParserElement",
+ toks: ParseResults,
+ cache_hit: bool = False,
+):
+ cache_hit_str = "*" if cache_hit else ""
+ print("{}Matched {} -> {}".format(cache_hit_str, expr, toks.as_list()))
+
+
+def _default_exception_debug_action(
+ instring: str,
+ loc: int,
+ expr: "ParserElement",
+ exc: Exception,
+ cache_hit: bool = False,
+):
+ cache_hit_str = "*" if cache_hit else ""
+ print(
+ "{}Match {} failed, {} raised: {}".format(
+ cache_hit_str, expr, type(exc).__name__, exc
+ )
+ )
+
+
+def null_debug_action(*args):
+ """'Do-nothing' debug action, to suppress debugging output during parsing."""
+
+
+class ParserElement(ABC):
+ """Abstract base level parser element class."""
+
+ DEFAULT_WHITE_CHARS: str = " \n\t\r"
+ verbose_stacktrace: bool = False
+ _literalStringClass: typing.Optional[type] = None
+
+ @staticmethod
+ def set_default_whitespace_chars(chars: str) -> None:
+ r"""
+ Overrides the default whitespace chars
+
+ Example::
+
+ # default whitespace chars are space, <TAB> and newline
+ Word(alphas)[1, ...].parse_string("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl']
+
+ # change to just treat newline as significant
+ ParserElement.set_default_whitespace_chars(" \t")
+ Word(alphas)[1, ...].parse_string("abc def\nghi jkl") # -> ['abc', 'def']
+ """
+ ParserElement.DEFAULT_WHITE_CHARS = chars
+
+ # update whitespace all parse expressions defined in this module
+ for expr in _builtin_exprs:
+ if expr.copyDefaultWhiteChars:
+ expr.whiteChars = set(chars)
+
+ @staticmethod
+ def inline_literals_using(cls: type) -> None:
+ """
+ Set class to be used for inclusion of string literals into a parser.
+
+ Example::
+
+ # default literal class used is Literal
+ integer = Word(nums)
+ date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+ date_str.parse_string("1999/12/31") # -> ['1999', '/', '12', '/', '31']
+
+
+ # change to Suppress
+ ParserElement.inline_literals_using(Suppress)
+ date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+ date_str.parse_string("1999/12/31") # -> ['1999', '12', '31']
+ """
+ ParserElement._literalStringClass = cls
+
+ class DebugActions(NamedTuple):
+ debug_try: typing.Optional[DebugStartAction]
+ debug_match: typing.Optional[DebugSuccessAction]
+ debug_fail: typing.Optional[DebugExceptionAction]
+
+ def __init__(self, savelist: bool = False):
+ self.parseAction: List[ParseAction] = list()
+ self.failAction: typing.Optional[ParseFailAction] = None
+ self.customName = None
+ self._defaultName = None
+ self.resultsName = None
+ self.saveAsList = savelist
+ self.skipWhitespace = True
+ self.whiteChars = set(ParserElement.DEFAULT_WHITE_CHARS)
+ self.copyDefaultWhiteChars = True
+ # used when checking for left-recursion
+ self.mayReturnEmpty = False
+ self.keepTabs = False
+ self.ignoreExprs: List["ParserElement"] = list()
+ self.debug = False
+ self.streamlined = False
+ # optimize exception handling for subclasses that don't advance parse index
+ self.mayIndexError = True
+ self.errmsg = ""
+ # mark results names as modal (report only last) or cumulative (list all)
+ self.modalResults = True
+ # custom debug actions
+ self.debugActions = self.DebugActions(None, None, None)
+ # avoid redundant calls to preParse
+ self.callPreparse = True
+ self.callDuringTry = False
+ self.suppress_warnings_: List[Diagnostics] = []
+
+ def suppress_warning(self, warning_type: Diagnostics) -> "ParserElement":
+ """
+ Suppress warnings emitted for a particular diagnostic on this expression.
+
+ Example::
+
+ base = pp.Forward()
+ base.suppress_warning(Diagnostics.warn_on_parse_using_empty_Forward)
+
+ # statement would normally raise a warning, but is now suppressed
+ print(base.parseString("x"))
+
+ """
+ self.suppress_warnings_.append(warning_type)
+ return self
+
+ def copy(self) -> "ParserElement":
+ """
+ Make a copy of this :class:`ParserElement`. Useful for defining
+ different parse actions for the same parsing pattern, using copies of
+ the original parse element.
+
+ Example::
+
+ integer = Word(nums).set_parse_action(lambda toks: int(toks[0]))
+ integerK = integer.copy().add_parse_action(lambda toks: toks[0] * 1024) + Suppress("K")
+ integerM = integer.copy().add_parse_action(lambda toks: toks[0] * 1024 * 1024) + Suppress("M")
+
+ print((integerK | integerM | integer)[1, ...].parse_string("5K 100 640K 256M"))
+
+ prints::
+
+ [5120, 100, 655360, 268435456]
+
+ Equivalent form of ``expr.copy()`` is just ``expr()``::
+
+ integerM = integer().add_parse_action(lambda toks: toks[0] * 1024 * 1024) + Suppress("M")
+ """
+ cpy = copy.copy(self)
+ cpy.parseAction = self.parseAction[:]
+ cpy.ignoreExprs = self.ignoreExprs[:]
+ if self.copyDefaultWhiteChars:
+ cpy.whiteChars = set(ParserElement.DEFAULT_WHITE_CHARS)
+ return cpy
+
+ def set_results_name(
+ self, name: str, list_all_matches: bool = False, *, listAllMatches: bool = False
+ ) -> "ParserElement":
+ """
+ Define name for referencing matching tokens as a nested attribute
+ of the returned parse results.
+
+ Normally, results names are assigned as you would assign keys in a dict:
+ any existing value is overwritten by later values. If it is necessary to
+ keep all values captured for a particular results name, call ``set_results_name``
+ with ``list_all_matches`` = True.
+
+ NOTE: ``set_results_name`` returns a *copy* of the original :class:`ParserElement` object;
+ this is so that the client can define a basic element, such as an
+ integer, and reference it in multiple places with different names.
+
+ You can also set results names using the abbreviated syntax,
+ ``expr("name")`` in place of ``expr.set_results_name("name")``
+ - see :class:`__call__`. If ``list_all_matches`` is required, use
+ ``expr("name*")``.
+
+ Example::
+
+ date_str = (integer.set_results_name("year") + '/'
+ + integer.set_results_name("month") + '/'
+ + integer.set_results_name("day"))
+
+ # equivalent form:
+ date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+ """
+ listAllMatches = listAllMatches or list_all_matches
+ return self._setResultsName(name, listAllMatches)
+
+ def _setResultsName(self, name, listAllMatches=False):
+ if name is None:
+ return self
+ newself = self.copy()
+ if name.endswith("*"):
+ name = name[:-1]
+ listAllMatches = True
+ newself.resultsName = name
+ newself.modalResults = not listAllMatches
+ return newself
+
+ def set_break(self, break_flag: bool = True) -> "ParserElement":
+ """
+ Method to invoke the Python pdb debugger when this element is
+ about to be parsed. Set ``break_flag`` to ``True`` to enable, ``False`` to
+ disable.
+ """
+ if break_flag:
+ _parseMethod = self._parse
+
+ def breaker(instring, loc, doActions=True, callPreParse=True):
+ import pdb
+
+ # this call to pdb.set_trace() is intentional, not a checkin error
+ pdb.set_trace()
+ return _parseMethod(instring, loc, doActions, callPreParse)
+
+ breaker._originalParseMethod = _parseMethod
+ self._parse = breaker
+ else:
+ if hasattr(self._parse, "_originalParseMethod"):
+ self._parse = self._parse._originalParseMethod
+ return self
+
+ def set_parse_action(self, *fns: ParseAction, **kwargs) -> "ParserElement":
+ """
+ Define one or more actions to perform when successfully matching parse element definition.
+
+ Parse actions can be called to perform data conversions, do extra validation,
+ update external data structures, or enhance or replace the parsed tokens.
+ Each parse action ``fn`` is a callable method with 0-3 arguments, called as
+ ``fn(s, loc, toks)`` , ``fn(loc, toks)`` , ``fn(toks)`` , or just ``fn()`` , where:
+
+ - s = the original string being parsed (see note below)
+ - loc = the location of the matching substring
+ - toks = a list of the matched tokens, packaged as a :class:`ParseResults` object
+
+ The parsed tokens are passed to the parse action as ParseResults. They can be
+ modified in place using list-style append, extend, and pop operations to update
+ the parsed list elements; and with dictionary-style item set and del operations
+ to add, update, or remove any named results. If the tokens are modified in place,
+ it is not necessary to return them with a return statement.
+
+ Parse actions can also completely replace the given tokens, with another ``ParseResults``
+ object, or with some entirely different object (common for parse actions that perform data
+ conversions). A convenient way to build a new parse result is to define the values
+ using a dict, and then create the return value using :class:`ParseResults.from_dict`.
+
+ If None is passed as the ``fn`` parse action, all previously added parse actions for this
+ expression are cleared.
+
+ Optional keyword arguments:
+
+ - call_during_try = (default= ``False``) indicate if parse action should be run during
+ lookaheads and alternate testing. For parse actions that have side effects, it is
+ important to only call the parse action once it is determined that it is being
+ called as part of a successful parse. For parse actions that perform additional
+ validation, then call_during_try should be passed as True, so that the validation
+ code is included in the preliminary "try" parses.
+
+ Note: the default parsing behavior is to expand tabs in the input string
+ before starting the parsing process. See :class:`parse_string` for more
+ information on parsing strings containing ``<TAB>`` s, and suggested
+ methods to maintain a consistent view of the parsed string, the parse
+ location, and line and column positions within the parsed string.
+
+ Example::
+
+ # parse dates in the form YYYY/MM/DD
+
+ # use parse action to convert toks from str to int at parse time
+ def convert_to_int(toks):
+ return int(toks[0])
+
+ # use a parse action to verify that the date is a valid date
+ def is_valid_date(instring, loc, toks):
+ from datetime import date
+ year, month, day = toks[::2]
+ try:
+ date(year, month, day)
+ except ValueError:
+ raise ParseException(instring, loc, "invalid date given")
+
+ integer = Word(nums)
+ date_str = integer + '/' + integer + '/' + integer
+
+ # add parse actions
+ integer.set_parse_action(convert_to_int)
+ date_str.set_parse_action(is_valid_date)
+
+ # note that integer fields are now ints, not strings
+ date_str.run_tests('''
+ # successful parse - note that integer fields were converted to ints
+ 1999/12/31
+
+ # fail - invalid date
+ 1999/13/31
+ ''')
+ """
+ if list(fns) == [None]:
+ self.parseAction = []
+ else:
+ if not all(callable(fn) for fn in fns):
+ raise TypeError("parse actions must be callable")
+ self.parseAction = [_trim_arity(fn) for fn in fns]
+ self.callDuringTry = kwargs.get(
+ "call_during_try", kwargs.get("callDuringTry", False)
+ )
+ return self
+
+ def add_parse_action(self, *fns: ParseAction, **kwargs) -> "ParserElement":
+ """
+ Add one or more parse actions to expression's list of parse actions. See :class:`set_parse_action`.
+
+ See examples in :class:`copy`.
+ """
+ self.parseAction += [_trim_arity(fn) for fn in fns]
+ self.callDuringTry = self.callDuringTry or kwargs.get(
+ "call_during_try", kwargs.get("callDuringTry", False)
+ )
+ return self
+
+ def add_condition(self, *fns: ParseCondition, **kwargs) -> "ParserElement":
+ """Add a boolean predicate function to expression's list of parse actions. See
+ :class:`set_parse_action` for function call signatures. Unlike ``set_parse_action``,
+ functions passed to ``add_condition`` need to return boolean success/fail of the condition.
+
+ Optional keyword arguments:
+
+ - message = define a custom message to be used in the raised exception
+ - fatal = if True, will raise ParseFatalException to stop parsing immediately; otherwise will raise
+ ParseException
+ - call_during_try = boolean to indicate if this method should be called during internal tryParse calls,
+ default=False
+
+ Example::
+
+ integer = Word(nums).set_parse_action(lambda toks: int(toks[0]))
+ year_int = integer.copy()
+ year_int.add_condition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later")
+ date_str = year_int + '/' + integer + '/' + integer
+
+ result = date_str.parse_string("1999/12/31") # -> Exception: Only support years 2000 and later (at char 0),
+ (line:1, col:1)
+ """
+ for fn in fns:
+ self.parseAction.append(
+ condition_as_parse_action(
+ fn, message=kwargs.get("message"), fatal=kwargs.get("fatal", False)
+ )
+ )
+
+ self.callDuringTry = self.callDuringTry or kwargs.get(
+ "call_during_try", kwargs.get("callDuringTry", False)
+ )
+ return self
+
+ def set_fail_action(self, fn: ParseFailAction) -> "ParserElement":
+ """
+ Define action to perform if parsing fails at this expression.
+ Fail acton fn is a callable function that takes the arguments
+ ``fn(s, loc, expr, err)`` where:
+
+ - s = string being parsed
+ - loc = location where expression match was attempted and failed
+ - expr = the parse expression that failed
+ - err = the exception thrown
+
+ The function returns no value. It may throw :class:`ParseFatalException`
+ if it is desired to stop parsing immediately."""
+ self.failAction = fn
+ return self
+
+ def _skipIgnorables(self, instring, loc):
+ exprsFound = True
+ while exprsFound:
+ exprsFound = False
+ for e in self.ignoreExprs:
+ try:
+ while 1:
+ loc, dummy = e._parse(instring, loc)
+ exprsFound = True
+ except ParseException:
+ pass
+ return loc
+
+ def preParse(self, instring, loc):
+ if self.ignoreExprs:
+ loc = self._skipIgnorables(instring, loc)
+
+ if self.skipWhitespace:
+ instrlen = len(instring)
+ white_chars = self.whiteChars
+ while loc < instrlen and instring[loc] in white_chars:
+ loc += 1
+
+ return loc
+
+ def parseImpl(self, instring, loc, doActions=True):
+ return loc, []
+
+ def postParse(self, instring, loc, tokenlist):
+ return tokenlist
+
+ # @profile
+ def _parseNoCache(
+ self, instring, loc, doActions=True, callPreParse=True
+ ) -> Tuple[int, ParseResults]:
+ TRY, MATCH, FAIL = 0, 1, 2
+ debugging = self.debug # and doActions)
+ len_instring = len(instring)
+
+ if debugging or self.failAction:
+ # print("Match {} at loc {}({}, {})".format(self, loc, lineno(loc, instring), col(loc, instring)))
+ try:
+ if callPreParse and self.callPreparse:
+ pre_loc = self.preParse(instring, loc)
+ else:
+ pre_loc = loc
+ tokens_start = pre_loc
+ if self.debugActions.debug_try:
+ self.debugActions.debug_try(instring, tokens_start, self, False)
+ if self.mayIndexError or pre_loc >= len_instring:
+ try:
+ loc, tokens = self.parseImpl(instring, pre_loc, doActions)
+ except IndexError:
+ raise ParseException(instring, len_instring, self.errmsg, self)
+ else:
+ loc, tokens = self.parseImpl(instring, pre_loc, doActions)
+ except Exception as err:
+ # print("Exception raised:", err)
+ if self.debugActions.debug_fail:
+ self.debugActions.debug_fail(
+ instring, tokens_start, self, err, False
+ )
+ if self.failAction:
+ self.failAction(instring, tokens_start, self, err)
+ raise
+ else:
+ if callPreParse and self.callPreparse:
+ pre_loc = self.preParse(instring, loc)
+ else:
+ pre_loc = loc
+ tokens_start = pre_loc
+ if self.mayIndexError or pre_loc >= len_instring:
+ try:
+ loc, tokens = self.parseImpl(instring, pre_loc, doActions)
+ except IndexError:
+ raise ParseException(instring, len_instring, self.errmsg, self)
+ else:
+ loc, tokens = self.parseImpl(instring, pre_loc, doActions)
+
+ tokens = self.postParse(instring, loc, tokens)
+
+ ret_tokens = ParseResults(
+ tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults
+ )
+ if self.parseAction and (doActions or self.callDuringTry):
+ if debugging:
+ try:
+ for fn in self.parseAction:
+ try:
+ tokens = fn(instring, tokens_start, ret_tokens)
+ except IndexError as parse_action_exc:
+ exc = ParseException("exception raised in parse action")
+ raise exc from parse_action_exc
+
+ if tokens is not None and tokens is not ret_tokens:
+ ret_tokens = ParseResults(
+ tokens,
+ self.resultsName,
+ asList=self.saveAsList
+ and isinstance(tokens, (ParseResults, list)),
+ modal=self.modalResults,
+ )
+ except Exception as err:
+ # print "Exception raised in user parse action:", err
+ if self.debugActions.debug_fail:
+ self.debugActions.debug_fail(
+ instring, tokens_start, self, err, False
+ )
+ raise
+ else:
+ for fn in self.parseAction:
+ try:
+ tokens = fn(instring, tokens_start, ret_tokens)
+ except IndexError as parse_action_exc:
+ exc = ParseException("exception raised in parse action")
+ raise exc from parse_action_exc
+
+ if tokens is not None and tokens is not ret_tokens:
+ ret_tokens = ParseResults(
+ tokens,
+ self.resultsName,
+ asList=self.saveAsList
+ and isinstance(tokens, (ParseResults, list)),
+ modal=self.modalResults,
+ )
+ if debugging:
+ # print("Matched", self, "->", ret_tokens.as_list())
+ if self.debugActions.debug_match:
+ self.debugActions.debug_match(
+ instring, tokens_start, loc, self, ret_tokens, False
+ )
+
+ return loc, ret_tokens
+
+ def try_parse(self, instring: str, loc: int, raise_fatal: bool = False) -> int:
+ try:
+ return self._parse(instring, loc, doActions=False)[0]
+ except ParseFatalException:
+ if raise_fatal:
+ raise
+ raise ParseException(instring, loc, self.errmsg, self)
+
+ def can_parse_next(self, instring: str, loc: int) -> bool:
+ try:
+ self.try_parse(instring, loc)
+ except (ParseException, IndexError):
+ return False
+ else:
+ return True
+
+ # cache for left-recursion in Forward references
+ recursion_lock = RLock()
+ recursion_memos: typing.Dict[
+ Tuple[int, "Forward", bool], Tuple[int, Union[ParseResults, Exception]]
+ ] = {}
+
+ # argument cache for optimizing repeated calls when backtracking through recursive expressions
+ packrat_cache = (
+ {}
+ ) # this is set later by enabled_packrat(); this is here so that reset_cache() doesn't fail
+ packrat_cache_lock = RLock()
+ packrat_cache_stats = [0, 0]
+
+ # this method gets repeatedly called during backtracking with the same arguments -
+ # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression
+ def _parseCache(
+ self, instring, loc, doActions=True, callPreParse=True
+ ) -> Tuple[int, ParseResults]:
+ HIT, MISS = 0, 1
+ TRY, MATCH, FAIL = 0, 1, 2
+ lookup = (self, instring, loc, callPreParse, doActions)
+ with ParserElement.packrat_cache_lock:
+ cache = ParserElement.packrat_cache
+ value = cache.get(lookup)
+ if value is cache.not_in_cache:
+ ParserElement.packrat_cache_stats[MISS] += 1
+ try:
+ value = self._parseNoCache(instring, loc, doActions, callPreParse)
+ except ParseBaseException as pe:
+ # cache a copy of the exception, without the traceback
+ cache.set(lookup, pe.__class__(*pe.args))
+ raise
+ else:
+ cache.set(lookup, (value[0], value[1].copy(), loc))
+ return value
+ else:
+ ParserElement.packrat_cache_stats[HIT] += 1
+ if self.debug and self.debugActions.debug_try:
+ try:
+ self.debugActions.debug_try(instring, loc, self, cache_hit=True)
+ except TypeError:
+ pass
+ if isinstance(value, Exception):
+ if self.debug and self.debugActions.debug_fail:
+ try:
+ self.debugActions.debug_fail(
+ instring, loc, self, value, cache_hit=True
+ )
+ except TypeError:
+ pass
+ raise value
+
+ loc_, result, endloc = value[0], value[1].copy(), value[2]
+ if self.debug and self.debugActions.debug_match:
+ try:
+ self.debugActions.debug_match(
+ instring, loc_, endloc, self, result, cache_hit=True
+ )
+ except TypeError:
+ pass
+
+ return loc_, result
+
+ _parse = _parseNoCache
+
+ @staticmethod
+ def reset_cache() -> None:
+ ParserElement.packrat_cache.clear()
+ ParserElement.packrat_cache_stats[:] = [0] * len(
+ ParserElement.packrat_cache_stats
+ )
+ ParserElement.recursion_memos.clear()
+
+ _packratEnabled = False
+ _left_recursion_enabled = False
+
+ @staticmethod
+ def disable_memoization() -> None:
+ """
+ Disables active Packrat or Left Recursion parsing and their memoization
+
+ This method also works if neither Packrat nor Left Recursion are enabled.
+ This makes it safe to call before activating Packrat nor Left Recursion
+ to clear any previous settings.
+ """
+ ParserElement.reset_cache()
+ ParserElement._left_recursion_enabled = False
+ ParserElement._packratEnabled = False
+ ParserElement._parse = ParserElement._parseNoCache
+
+ @staticmethod
+ def enable_left_recursion(
+ cache_size_limit: typing.Optional[int] = None, *, force=False
+ ) -> None:
+ """
+ Enables "bounded recursion" parsing, which allows for both direct and indirect
+ left-recursion. During parsing, left-recursive :class:`Forward` elements are
+ repeatedly matched with a fixed recursion depth that is gradually increased
+ until finding the longest match.
+
+ Example::
+
+ from pip._vendor import pyparsing as pp
+ pp.ParserElement.enable_left_recursion()
+
+ E = pp.Forward("E")
+ num = pp.Word(pp.nums)
+ # match `num`, or `num '+' num`, or `num '+' num '+' num`, ...
+ E <<= E + '+' - num | num
+
+ print(E.parse_string("1+2+3"))
+
+ Recursion search naturally memoizes matches of ``Forward`` elements and may
+ thus skip reevaluation of parse actions during backtracking. This may break
+ programs with parse actions which rely on strict ordering of side-effects.
+
+ Parameters:
+
+ - cache_size_limit - (default=``None``) - memoize at most this many
+ ``Forward`` elements during matching; if ``None`` (the default),
+ memoize all ``Forward`` elements.
+
+ Bounded Recursion parsing works similar but not identical to Packrat parsing,
+ thus the two cannot be used together. Use ``force=True`` to disable any
+ previous, conflicting settings.
+ """
+ if force:
+ ParserElement.disable_memoization()
+ elif ParserElement._packratEnabled:
+ raise RuntimeError("Packrat and Bounded Recursion are not compatible")
+ if cache_size_limit is None:
+ ParserElement.recursion_memos = _UnboundedMemo()
+ elif cache_size_limit > 0:
+ ParserElement.recursion_memos = _LRUMemo(capacity=cache_size_limit)
+ else:
+ raise NotImplementedError("Memo size of %s" % cache_size_limit)
+ ParserElement._left_recursion_enabled = True
+
+ @staticmethod
+ def enable_packrat(cache_size_limit: int = 128, *, force: bool = False) -> None:
+ """
+ Enables "packrat" parsing, which adds memoizing to the parsing logic.
+ Repeated parse attempts at the same string location (which happens
+ often in many complex grammars) can immediately return a cached value,
+ instead of re-executing parsing/validating code. Memoizing is done of
+ both valid results and parsing exceptions.
+
+ Parameters:
+
+ - cache_size_limit - (default= ``128``) - if an integer value is provided
+ will limit the size of the packrat cache; if None is passed, then
+ the cache size will be unbounded; if 0 is passed, the cache will
+ be effectively disabled.
+
+ This speedup may break existing programs that use parse actions that
+ have side-effects. For this reason, packrat parsing is disabled when
+ you first import pyparsing. To activate the packrat feature, your
+ program must call the class method :class:`ParserElement.enable_packrat`.
+ For best results, call ``enable_packrat()`` immediately after
+ importing pyparsing.
+
+ Example::
+
+ from pip._vendor import pyparsing
+ pyparsing.ParserElement.enable_packrat()
+
+ Packrat parsing works similar but not identical to Bounded Recursion parsing,
+ thus the two cannot be used together. Use ``force=True`` to disable any
+ previous, conflicting settings.
+ """
+ if force:
+ ParserElement.disable_memoization()
+ elif ParserElement._left_recursion_enabled:
+ raise RuntimeError("Packrat and Bounded Recursion are not compatible")
+ if not ParserElement._packratEnabled:
+ ParserElement._packratEnabled = True
+ if cache_size_limit is None:
+ ParserElement.packrat_cache = _UnboundedCache()
+ else:
+ ParserElement.packrat_cache = _FifoCache(cache_size_limit)
+ ParserElement._parse = ParserElement._parseCache
+
+ def parse_string(
+ self, instring: str, parse_all: bool = False, *, parseAll: bool = False
+ ) -> ParseResults:
+ """
+ Parse a string with respect to the parser definition. This function is intended as the primary interface to the
+ client code.
+
+ :param instring: The input string to be parsed.
+ :param parse_all: If set, the entire input string must match the grammar.
+ :param parseAll: retained for pre-PEP8 compatibility, will be removed in a future release.
+ :raises ParseException: Raised if ``parse_all`` is set and the input string does not match the whole grammar.
+ :returns: the parsed data as a :class:`ParseResults` object, which may be accessed as a `list`, a `dict`, or
+ an object with attributes if the given parser includes results names.
+
+ If the input string is required to match the entire grammar, ``parse_all`` flag must be set to ``True``. This
+ is also equivalent to ending the grammar with :class:`StringEnd`().
+
+ To report proper column numbers, ``parse_string`` operates on a copy of the input string where all tabs are
+ converted to spaces (8 spaces per tab, as per the default in ``string.expandtabs``). If the input string
+ contains tabs and the grammar uses parse actions that use the ``loc`` argument to index into the string
+ being parsed, one can ensure a consistent view of the input string by doing one of the following:
+
+ - calling ``parse_with_tabs`` on your grammar before calling ``parse_string`` (see :class:`parse_with_tabs`),
+ - define your parse action using the full ``(s,loc,toks)`` signature, and reference the input string using the
+ parse action's ``s`` argument, or
+ - explicitly expand the tabs in your input string before calling ``parse_string``.
+
+ Examples:
+
+ By default, partial matches are OK.
+
+ >>> res = Word('a').parse_string('aaaaabaaa')
+ >>> print(res)
+ ['aaaaa']
+
+ The parsing behavior varies by the inheriting class of this abstract class. Please refer to the children
+ directly to see more examples.
+
+ It raises an exception if parse_all flag is set and instring does not match the whole grammar.
+
+ >>> res = Word('a').parse_string('aaaaabaaa', parse_all=True)
+ Traceback (most recent call last):
+ ...
+ pyparsing.ParseException: Expected end of text, found 'b' (at char 5), (line:1, col:6)
+ """
+ parseAll = parse_all or parseAll
+
+ ParserElement.reset_cache()
+ if not self.streamlined:
+ self.streamline()
+ for e in self.ignoreExprs:
+ e.streamline()
+ if not self.keepTabs:
+ instring = instring.expandtabs()
+ try:
+ loc, tokens = self._parse(instring, 0)
+ if parseAll:
+ loc = self.preParse(instring, loc)
+ se = Empty() + StringEnd()
+ se._parse(instring, loc)
+ except ParseBaseException as exc:
+ if ParserElement.verbose_stacktrace:
+ raise
+ else:
+ # catch and re-raise exception from here, clearing out pyparsing internal stack trace
+ raise exc.with_traceback(None)
+ else:
+ return tokens
+
+ def scan_string(
+ self,
+ instring: str,
+ max_matches: int = _MAX_INT,
+ overlap: bool = False,
+ *,
+ debug: bool = False,
+ maxMatches: int = _MAX_INT,
+ ) -> Generator[Tuple[ParseResults, int, int], None, None]:
+ """
+ Scan the input string for expression matches. Each match will return the
+ matching tokens, start location, and end location. May be called with optional
+ ``max_matches`` argument, to clip scanning after 'n' matches are found. If
+ ``overlap`` is specified, then overlapping matches will be reported.
+
+ Note that the start and end locations are reported relative to the string
+ being parsed. See :class:`parse_string` for more information on parsing
+ strings with embedded tabs.
+
+ Example::
+
+ source = "sldjf123lsdjjkf345sldkjf879lkjsfd987"
+ print(source)
+ for tokens, start, end in Word(alphas).scan_string(source):
+ print(' '*start + '^'*(end-start))
+ print(' '*start + tokens[0])
+
+ prints::
+
+ sldjf123lsdjjkf345sldkjf879lkjsfd987
+ ^^^^^
+ sldjf
+ ^^^^^^^
+ lsdjjkf
+ ^^^^^^
+ sldkjf
+ ^^^^^^
+ lkjsfd
+ """
+ maxMatches = min(maxMatches, max_matches)
+ if not self.streamlined:
+ self.streamline()
+ for e in self.ignoreExprs:
+ e.streamline()
+
+ if not self.keepTabs:
+ instring = str(instring).expandtabs()
+ instrlen = len(instring)
+ loc = 0
+ preparseFn = self.preParse
+ parseFn = self._parse
+ ParserElement.resetCache()
+ matches = 0
+ try:
+ while loc <= instrlen and matches < maxMatches:
+ try:
+ preloc = preparseFn(instring, loc)
+ nextLoc, tokens = parseFn(instring, preloc, callPreParse=False)
+ except ParseException:
+ loc = preloc + 1
+ else:
+ if nextLoc > loc:
+ matches += 1
+ if debug:
+ print(
+ {
+ "tokens": tokens.asList(),
+ "start": preloc,
+ "end": nextLoc,
+ }
+ )
+ yield tokens, preloc, nextLoc
+ if overlap:
+ nextloc = preparseFn(instring, loc)
+ if nextloc > loc:
+ loc = nextLoc
+ else:
+ loc += 1
+ else:
+ loc = nextLoc
+ else:
+ loc = preloc + 1
+ except ParseBaseException as exc:
+ if ParserElement.verbose_stacktrace:
+ raise
+ else:
+ # catch and re-raise exception from here, clears out pyparsing internal stack trace
+ raise exc.with_traceback(None)
+
+ def transform_string(self, instring: str, *, debug: bool = False) -> str:
+ """
+ Extension to :class:`scan_string`, to modify matching text with modified tokens that may
+ be returned from a parse action. To use ``transform_string``, define a grammar and
+ attach a parse action to it that modifies the returned token list.
+ Invoking ``transform_string()`` on a target string will then scan for matches,
+ and replace the matched text patterns according to the logic in the parse
+ action. ``transform_string()`` returns the resulting transformed string.
+
+ Example::
+
+ wd = Word(alphas)
+ wd.set_parse_action(lambda toks: toks[0].title())
+
+ print(wd.transform_string("now is the winter of our discontent made glorious summer by this sun of york."))
+
+ prints::
+
+ Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York.
+ """
+ out: List[str] = []
+ lastE = 0
+ # force preservation of <TAB>s, to minimize unwanted transformation of string, and to
+ # keep string locs straight between transform_string and scan_string
+ self.keepTabs = True
+ try:
+ for t, s, e in self.scan_string(instring, debug=debug):
+ out.append(instring[lastE:s])
+ if t:
+ if isinstance(t, ParseResults):
+ out += t.as_list()
+ elif isinstance(t, Iterable) and not isinstance(t, str_type):
+ out.extend(t)
+ else:
+ out.append(t)
+ lastE = e
+ out.append(instring[lastE:])
+ out = [o for o in out if o]
+ return "".join([str(s) for s in _flatten(out)])
+ except ParseBaseException as exc:
+ if ParserElement.verbose_stacktrace:
+ raise
+ else:
+ # catch and re-raise exception from here, clears out pyparsing internal stack trace
+ raise exc.with_traceback(None)
+
+ def search_string(
+ self,
+ instring: str,
+ max_matches: int = _MAX_INT,
+ *,
+ debug: bool = False,
+ maxMatches: int = _MAX_INT,
+ ) -> ParseResults:
+ """
+ Another extension to :class:`scan_string`, simplifying the access to the tokens found
+ to match the given parse expression. May be called with optional
+ ``max_matches`` argument, to clip searching after 'n' matches are found.
+
+ Example::
+
+ # a capitalized word starts with an uppercase letter, followed by zero or more lowercase letters
+ cap_word = Word(alphas.upper(), alphas.lower())
+
+ print(cap_word.search_string("More than Iron, more than Lead, more than Gold I need Electricity"))
+
+ # the sum() builtin can be used to merge results into a single ParseResults object
+ print(sum(cap_word.search_string("More than Iron, more than Lead, more than Gold I need Electricity")))
+
+ prints::
+
+ [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']]
+ ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity']
+ """
+ maxMatches = min(maxMatches, max_matches)
+ try:
+ return ParseResults(
+ [t for t, s, e in self.scan_string(instring, maxMatches, debug=debug)]
+ )
+ except ParseBaseException as exc:
+ if ParserElement.verbose_stacktrace:
+ raise
+ else:
+ # catch and re-raise exception from here, clears out pyparsing internal stack trace
+ raise exc.with_traceback(None)
+
+ def split(
+ self,
+ instring: str,
+ maxsplit: int = _MAX_INT,
+ include_separators: bool = False,
+ *,
+ includeSeparators=False,
+ ) -> Generator[str, None, None]:
+ """
+ Generator method to split a string using the given expression as a separator.
+ May be called with optional ``maxsplit`` argument, to limit the number of splits;
+ and the optional ``include_separators`` argument (default= ``False``), if the separating
+ matching text should be included in the split results.
+
+ Example::
+
+ punc = one_of(list(".,;:/-!?"))
+ print(list(punc.split("This, this?, this sentence, is badly punctuated!")))
+
+ prints::
+
+ ['This', ' this', '', ' this sentence', ' is badly punctuated', '']
+ """
+ includeSeparators = includeSeparators or include_separators
+ last = 0
+ for t, s, e in self.scan_string(instring, max_matches=maxsplit):
+ yield instring[last:s]
+ if includeSeparators:
+ yield t[0]
+ last = e
+ yield instring[last:]
+
+ def __add__(self, other) -> "ParserElement":
+ """
+ Implementation of ``+`` operator - returns :class:`And`. Adding strings to a :class:`ParserElement`
+ converts them to :class:`Literal`s by default.
+
+ Example::
+
+ greet = Word(alphas) + "," + Word(alphas) + "!"
+ hello = "Hello, World!"
+ print(hello, "->", greet.parse_string(hello))
+
+ prints::
+
+ Hello, World! -> ['Hello', ',', 'World', '!']
+
+ ``...`` may be used as a parse expression as a short form of :class:`SkipTo`.
+
+ Literal('start') + ... + Literal('end')
+
+ is equivalent to:
+
+ Literal('start') + SkipTo('end')("_skipped*") + Literal('end')
+
+ Note that the skipped text is returned with '_skipped' as a results name,
+ and to support having multiple skips in the same parser, the value returned is
+ a list of all skipped text.
+ """
+ if other is Ellipsis:
+ return _PendingSkip(self)
+
+ if isinstance(other, str_type):
+ other = self._literalStringClass(other)
+ if not isinstance(other, ParserElement):
+ raise TypeError(
+ "Cannot combine element of type {} with ParserElement".format(
+ type(other).__name__
+ )
+ )
+ return And([self, other])
+
+ def __radd__(self, other) -> "ParserElement":
+ """
+ Implementation of ``+`` operator when left operand is not a :class:`ParserElement`
+ """
+ if other is Ellipsis:
+ return SkipTo(self)("_skipped*") + self
+
+ if isinstance(other, str_type):
+ other = self._literalStringClass(other)
+ if not isinstance(other, ParserElement):
+ raise TypeError(
+ "Cannot combine element of type {} with ParserElement".format(
+ type(other).__name__
+ )
+ )
+ return other + self
+
+ def __sub__(self, other) -> "ParserElement":
+ """
+ Implementation of ``-`` operator, returns :class:`And` with error stop
+ """
+ if isinstance(other, str_type):
+ other = self._literalStringClass(other)
+ if not isinstance(other, ParserElement):
+ raise TypeError(
+ "Cannot combine element of type {} with ParserElement".format(
+ type(other).__name__
+ )
+ )
+ return self + And._ErrorStop() + other
+
+ def __rsub__(self, other) -> "ParserElement":
+ """
+ Implementation of ``-`` operator when left operand is not a :class:`ParserElement`
+ """
+ if isinstance(other, str_type):
+ other = self._literalStringClass(other)
+ if not isinstance(other, ParserElement):
+ raise TypeError(
+ "Cannot combine element of type {} with ParserElement".format(
+ type(other).__name__
+ )
+ )
+ return other - self
+
+ def __mul__(self, other) -> "ParserElement":
+ """
+ Implementation of ``*`` operator, allows use of ``expr * 3`` in place of
+ ``expr + expr + expr``. Expressions may also be multiplied by a 2-integer
+ tuple, similar to ``{min, max}`` multipliers in regular expressions. Tuples
+ may also include ``None`` as in:
+ - ``expr*(n, None)`` or ``expr*(n, )`` is equivalent
+ to ``expr*n + ZeroOrMore(expr)``
+ (read as "at least n instances of ``expr``")
+ - ``expr*(None, n)`` is equivalent to ``expr*(0, n)``
+ (read as "0 to n instances of ``expr``")
+ - ``expr*(None, None)`` is equivalent to ``ZeroOrMore(expr)``
+ - ``expr*(1, None)`` is equivalent to ``OneOrMore(expr)``
+
+ Note that ``expr*(None, n)`` does not raise an exception if
+ more than n exprs exist in the input stream; that is,
+ ``expr*(None, n)`` does not enforce a maximum number of expr
+ occurrences. If this behavior is desired, then write
+ ``expr*(None, n) + ~expr``
+ """
+ if other is Ellipsis:
+ other = (0, None)
+ elif isinstance(other, tuple) and other[:1] == (Ellipsis,):
+ other = ((0,) + other[1:] + (None,))[:2]
+
+ if isinstance(other, int):
+ minElements, optElements = other, 0
+ elif isinstance(other, tuple):
+ other = tuple(o if o is not Ellipsis else None for o in other)
+ other = (other + (None, None))[:2]
+ if other[0] is None:
+ other = (0, other[1])
+ if isinstance(other[0], int) and other[1] is None:
+ if other[0] == 0:
+ return ZeroOrMore(self)
+ if other[0] == 1:
+ return OneOrMore(self)
+ else:
+ return self * other[0] + ZeroOrMore(self)
+ elif isinstance(other[0], int) and isinstance(other[1], int):
+ minElements, optElements = other
+ optElements -= minElements
+ else:
+ raise TypeError(
+ "cannot multiply ParserElement and ({}) objects".format(
+ ",".join(type(item).__name__ for item in other)
+ )
+ )
+ else:
+ raise TypeError(
+ "cannot multiply ParserElement and {} objects".format(
+ type(other).__name__
+ )
+ )
+
+ if minElements < 0:
+ raise ValueError("cannot multiply ParserElement by negative value")
+ if optElements < 0:
+ raise ValueError(
+ "second tuple value must be greater or equal to first tuple value"
+ )
+ if minElements == optElements == 0:
+ return And([])
+
+ if optElements:
+
+ def makeOptionalList(n):
+ if n > 1:
+ return Opt(self + makeOptionalList(n - 1))
+ else:
+ return Opt(self)
+
+ if minElements:
+ if minElements == 1:
+ ret = self + makeOptionalList(optElements)
+ else:
+ ret = And([self] * minElements) + makeOptionalList(optElements)
+ else:
+ ret = makeOptionalList(optElements)
+ else:
+ if minElements == 1:
+ ret = self
+ else:
+ ret = And([self] * minElements)
+ return ret
+
+ def __rmul__(self, other) -> "ParserElement":
+ return self.__mul__(other)
+
+ def __or__(self, other) -> "ParserElement":
+ """
+ Implementation of ``|`` operator - returns :class:`MatchFirst`
+ """
+ if other is Ellipsis:
+ return _PendingSkip(self, must_skip=True)
+
+ if isinstance(other, str_type):
+ other = self._literalStringClass(other)
+ if not isinstance(other, ParserElement):
+ raise TypeError(
+ "Cannot combine element of type {} with ParserElement".format(
+ type(other).__name__
+ )
+ )
+ return MatchFirst([self, other])
+
+ def __ror__(self, other) -> "ParserElement":
+ """
+ Implementation of ``|`` operator when left operand is not a :class:`ParserElement`
+ """
+ if isinstance(other, str_type):
+ other = self._literalStringClass(other)
+ if not isinstance(other, ParserElement):
+ raise TypeError(
+ "Cannot combine element of type {} with ParserElement".format(
+ type(other).__name__
+ )
+ )
+ return other | self
+
+ def __xor__(self, other) -> "ParserElement":
+ """
+ Implementation of ``^`` operator - returns :class:`Or`
+ """
+ if isinstance(other, str_type):
+ other = self._literalStringClass(other)
+ if not isinstance(other, ParserElement):
+ raise TypeError(
+ "Cannot combine element of type {} with ParserElement".format(
+ type(other).__name__
+ )
+ )
+ return Or([self, other])
+
+ def __rxor__(self, other) -> "ParserElement":
+ """
+ Implementation of ``^`` operator when left operand is not a :class:`ParserElement`
+ """
+ if isinstance(other, str_type):
+ other = self._literalStringClass(other)
+ if not isinstance(other, ParserElement):
+ raise TypeError(
+ "Cannot combine element of type {} with ParserElement".format(
+ type(other).__name__
+ )
+ )
+ return other ^ self
+
+ def __and__(self, other) -> "ParserElement":
+ """
+ Implementation of ``&`` operator - returns :class:`Each`
+ """
+ if isinstance(other, str_type):
+ other = self._literalStringClass(other)
+ if not isinstance(other, ParserElement):
+ raise TypeError(
+ "Cannot combine element of type {} with ParserElement".format(
+ type(other).__name__
+ )
+ )
+ return Each([self, other])
+
+ def __rand__(self, other) -> "ParserElement":
+ """
+ Implementation of ``&`` operator when left operand is not a :class:`ParserElement`
+ """
+ if isinstance(other, str_type):
+ other = self._literalStringClass(other)
+ if not isinstance(other, ParserElement):
+ raise TypeError(
+ "Cannot combine element of type {} with ParserElement".format(
+ type(other).__name__
+ )
+ )
+ return other & self
+
+ def __invert__(self) -> "ParserElement":
+ """
+ Implementation of ``~`` operator - returns :class:`NotAny`
+ """
+ return NotAny(self)
+
+ # disable __iter__ to override legacy use of sequential access to __getitem__ to
+ # iterate over a sequence
+ __iter__ = None
+
+ def __getitem__(self, key):
+ """
+ use ``[]`` indexing notation as a short form for expression repetition:
+
+ - ``expr[n]`` is equivalent to ``expr*n``
+ - ``expr[m, n]`` is equivalent to ``expr*(m, n)``
+ - ``expr[n, ...]`` or ``expr[n,]`` is equivalent
+ to ``expr*n + ZeroOrMore(expr)``
+ (read as "at least n instances of ``expr``")
+ - ``expr[..., n]`` is equivalent to ``expr*(0, n)``
+ (read as "0 to n instances of ``expr``")
+ - ``expr[...]`` and ``expr[0, ...]`` are equivalent to ``ZeroOrMore(expr)``
+ - ``expr[1, ...]`` is equivalent to ``OneOrMore(expr)``
+
+ ``None`` may be used in place of ``...``.
+
+ Note that ``expr[..., n]`` and ``expr[m, n]``do not raise an exception
+ if more than ``n`` ``expr``s exist in the input stream. If this behavior is
+ desired, then write ``expr[..., n] + ~expr``.
+ """
+
+ # convert single arg keys to tuples
+ try:
+ if isinstance(key, str_type):
+ key = (key,)
+ iter(key)
+ except TypeError:
+ key = (key, key)
+
+ if len(key) > 2:
+ raise TypeError(
+ "only 1 or 2 index arguments supported ({}{})".format(
+ key[:5], "... [{}]".format(len(key)) if len(key) > 5 else ""
+ )
+ )
+
+ # clip to 2 elements
+ ret = self * tuple(key[:2])
+ return ret
+
+ def __call__(self, name: str = None) -> "ParserElement":
+ """
+ Shortcut for :class:`set_results_name`, with ``list_all_matches=False``.
+
+ If ``name`` is given with a trailing ``'*'`` character, then ``list_all_matches`` will be
+ passed as ``True``.
+
+ If ``name` is omitted, same as calling :class:`copy`.
+
+ Example::
+
+ # these are equivalent
+ userdata = Word(alphas).set_results_name("name") + Word(nums + "-").set_results_name("socsecno")
+ userdata = Word(alphas)("name") + Word(nums + "-")("socsecno")
+ """
+ if name is not None:
+ return self._setResultsName(name)
+ else:
+ return self.copy()
+
+ def suppress(self) -> "ParserElement":
+ """
+ Suppresses the output of this :class:`ParserElement`; useful to keep punctuation from
+ cluttering up returned output.
+ """
+ return Suppress(self)
+
+ def ignore_whitespace(self, recursive: bool = True) -> "ParserElement":
+ """
+ Enables the skipping of whitespace before matching the characters in the
+ :class:`ParserElement`'s defined pattern.
+
+ :param recursive: If ``True`` (the default), also enable whitespace skipping in child elements (if any)
+ """
+ self.skipWhitespace = True
+ return self
+
+ def leave_whitespace(self, recursive: bool = True) -> "ParserElement":
+ """
+ Disables the skipping of whitespace before matching the characters in the
+ :class:`ParserElement`'s defined pattern. This is normally only used internally by
+ the pyparsing module, but may be needed in some whitespace-sensitive grammars.
+
+ :param recursive: If true (the default), also disable whitespace skipping in child elements (if any)
+ """
+ self.skipWhitespace = False
+ return self
+
+ def set_whitespace_chars(
+ self, chars: Union[Set[str], str], copy_defaults: bool = False
+ ) -> "ParserElement":
+ """
+ Overrides the default whitespace chars
+ """
+ self.skipWhitespace = True
+ self.whiteChars = set(chars)
+ self.copyDefaultWhiteChars = copy_defaults
+ return self
+
+ def parse_with_tabs(self) -> "ParserElement":
+ """
+ Overrides default behavior to expand ``<TAB>`` s to spaces before parsing the input string.
+ Must be called before ``parse_string`` when the input grammar contains elements that
+ match ``<TAB>`` characters.
+ """
+ self.keepTabs = True
+ return self
+
+ def ignore(self, other: "ParserElement") -> "ParserElement":
+ """
+ Define expression to be ignored (e.g., comments) while doing pattern
+ matching; may be called repeatedly, to define multiple comment or other
+ ignorable patterns.
+
+ Example::
+
+ patt = Word(alphas)[1, ...]
+ patt.parse_string('ablaj /* comment */ lskjd')
+ # -> ['ablaj']
+
+ patt.ignore(c_style_comment)
+ patt.parse_string('ablaj /* comment */ lskjd')
+ # -> ['ablaj', 'lskjd']
+ """
+ import typing
+
+ if isinstance(other, str_type):
+ other = Suppress(other)
+
+ if isinstance(other, Suppress):
+ if other not in self.ignoreExprs:
+ self.ignoreExprs.append(other)
+ else:
+ self.ignoreExprs.append(Suppress(other.copy()))
+ return self
+
+ def set_debug_actions(
+ self,
+ start_action: DebugStartAction,
+ success_action: DebugSuccessAction,
+ exception_action: DebugExceptionAction,
+ ) -> "ParserElement":
+ """
+ Customize display of debugging messages while doing pattern matching:
+
+ - ``start_action`` - method to be called when an expression is about to be parsed;
+ should have the signature ``fn(input_string: str, location: int, expression: ParserElement, cache_hit: bool)``
+
+ - ``success_action`` - method to be called when an expression has successfully parsed;
+ should have the signature ``fn(input_string: str, start_location: int, end_location: int, expression: ParserELement, parsed_tokens: ParseResults, cache_hit: bool)``
+
+ - ``exception_action`` - method to be called when expression fails to parse;
+ should have the signature ``fn(input_string: str, location: int, expression: ParserElement, exception: Exception, cache_hit: bool)``
+ """
+ self.debugActions = self.DebugActions(
+ start_action or _default_start_debug_action,
+ success_action or _default_success_debug_action,
+ exception_action or _default_exception_debug_action,
+ )
+ self.debug = True
+ return self
+
+ def set_debug(self, flag: bool = True) -> "ParserElement":
+ """
+ Enable display of debugging messages while doing pattern matching.
+ Set ``flag`` to ``True`` to enable, ``False`` to disable.
+
+ Example::
+
+ wd = Word(alphas).set_name("alphaword")
+ integer = Word(nums).set_name("numword")
+ term = wd | integer
+
+ # turn on debugging for wd
+ wd.set_debug()
+
+ term[1, ...].parse_string("abc 123 xyz 890")
+
+ prints::
+
+ Match alphaword at loc 0(1,1)
+ Matched alphaword -> ['abc']
+ Match alphaword at loc 3(1,4)
+ Exception raised:Expected alphaword (at char 4), (line:1, col:5)
+ Match alphaword at loc 7(1,8)
+ Matched alphaword -> ['xyz']
+ Match alphaword at loc 11(1,12)
+ Exception raised:Expected alphaword (at char 12), (line:1, col:13)
+ Match alphaword at loc 15(1,16)
+ Exception raised:Expected alphaword (at char 15), (line:1, col:16)
+
+ The output shown is that produced by the default debug actions - custom debug actions can be
+ specified using :class:`set_debug_actions`. Prior to attempting
+ to match the ``wd`` expression, the debugging message ``"Match <exprname> at loc <n>(<line>,<col>)"``
+ is shown. Then if the parse succeeds, a ``"Matched"`` message is shown, or an ``"Exception raised"``
+ message is shown. Also note the use of :class:`set_name` to assign a human-readable name to the expression,
+ which makes debugging and exception messages easier to understand - for instance, the default
+ name created for the :class:`Word` expression without calling ``set_name`` is ``"W:(A-Za-z)"``.
+ """
+ if flag:
+ self.set_debug_actions(
+ _default_start_debug_action,
+ _default_success_debug_action,
+ _default_exception_debug_action,
+ )
+ else:
+ self.debug = False
+ return self
+
+ @property
+ def default_name(self) -> str:
+ if self._defaultName is None:
+ self._defaultName = self._generateDefaultName()
+ return self._defaultName
+
+ @abstractmethod
+ def _generateDefaultName(self):
+ """
+ Child classes must define this method, which defines how the ``default_name`` is set.
+ """
+
+ def set_name(self, name: str) -> "ParserElement":
+ """
+ Define name for this expression, makes debugging and exception messages clearer.
+ Example::
+ Word(nums).parse_string("ABC") # -> Exception: Expected W:(0-9) (at char 0), (line:1, col:1)
+ Word(nums).set_name("integer").parse_string("ABC") # -> Exception: Expected integer (at char 0), (line:1, col:1)
+ """
+ self.customName = name
+ self.errmsg = "Expected " + self.name
+ if __diag__.enable_debug_on_named_expressions:
+ self.set_debug()
+ return self
+
+ @property
+ def name(self) -> str:
+ # This will use a user-defined name if available, but otherwise defaults back to the auto-generated name
+ return self.customName if self.customName is not None else self.default_name
+
+ def __str__(self) -> str:
+ return self.name
+
+ def __repr__(self) -> str:
+ return str(self)
+
+ def streamline(self) -> "ParserElement":
+ self.streamlined = True
+ self._defaultName = None
+ return self
+
+ def recurse(self) -> Sequence["ParserElement"]:
+ return []
+
+ def _checkRecursion(self, parseElementList):
+ subRecCheckList = parseElementList[:] + [self]
+ for e in self.recurse():
+ e._checkRecursion(subRecCheckList)
+
+ def validate(self, validateTrace=None) -> None:
+ """
+ Check defined expressions for valid structure, check for infinite recursive definitions.
+ """
+ self._checkRecursion([])
+
+ def parse_file(
+ self,
+ file_or_filename: Union[str, Path, TextIO],
+ encoding: str = "utf-8",
+ parse_all: bool = False,
+ *,
+ parseAll: bool = False,
+ ) -> ParseResults:
+ """
+ Execute the parse expression on the given file or filename.
+ If a filename is specified (instead of a file object),
+ the entire file is opened, read, and closed before parsing.
+ """
+ parseAll = parseAll or parse_all
+ try:
+ file_contents = file_or_filename.read()
+ except AttributeError:
+ with open(file_or_filename, "r", encoding=encoding) as f:
+ file_contents = f.read()
+ try:
+ return self.parse_string(file_contents, parseAll)
+ except ParseBaseException as exc:
+ if ParserElement.verbose_stacktrace:
+ raise
+ else:
+ # catch and re-raise exception from here, clears out pyparsing internal stack trace
+ raise exc.with_traceback(None)
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ elif isinstance(other, str_type):
+ return self.matches(other, parse_all=True)
+ elif isinstance(other, ParserElement):
+ return vars(self) == vars(other)
+ return False
+
+ def __hash__(self):
+ return id(self)
+
+ def matches(
+ self, test_string: str, parse_all: bool = True, *, parseAll: bool = True
+ ) -> bool:
+ """
+ Method for quick testing of a parser against a test string. Good for simple
+ inline microtests of sub expressions while building up larger parser.
+
+ Parameters:
+ - ``test_string`` - to test against this expression for a match
+ - ``parse_all`` - (default= ``True``) - flag to pass to :class:`parse_string` when running tests
+
+ Example::
+
+ expr = Word(nums)
+ assert expr.matches("100")
+ """
+ parseAll = parseAll and parse_all
+ try:
+ self.parse_string(str(test_string), parse_all=parseAll)
+ return True
+ except ParseBaseException:
+ return False
+
+ def run_tests(
+ self,
+ tests: Union[str, List[str]],
+ parse_all: bool = True,
+ comment: typing.Optional[Union["ParserElement", str]] = "#",
+ full_dump: bool = True,
+ print_results: bool = True,
+ failure_tests: bool = False,
+ post_parse: Callable[[str, ParseResults], str] = None,
+ file: typing.Optional[TextIO] = None,
+ with_line_numbers: bool = False,
+ *,
+ parseAll: bool = True,
+ fullDump: bool = True,
+ printResults: bool = True,
+ failureTests: bool = False,
+ postParse: Callable[[str, ParseResults], str] = None,
+ ) -> Tuple[bool, List[Tuple[str, Union[ParseResults, Exception]]]]:
+ """
+ Execute the parse expression on a series of test strings, showing each
+ test, the parsed results or where the parse failed. Quick and easy way to
+ run a parse expression against a list of sample strings.
+
+ Parameters:
+ - ``tests`` - a list of separate test strings, or a multiline string of test strings
+ - ``parse_all`` - (default= ``True``) - flag to pass to :class:`parse_string` when running tests
+ - ``comment`` - (default= ``'#'``) - expression for indicating embedded comments in the test
+ string; pass None to disable comment filtering
+ - ``full_dump`` - (default= ``True``) - dump results as list followed by results names in nested outline;
+ if False, only dump nested list
+ - ``print_results`` - (default= ``True``) prints test output to stdout
+ - ``failure_tests`` - (default= ``False``) indicates if these tests are expected to fail parsing
+ - ``post_parse`` - (default= ``None``) optional callback for successful parse results; called as
+ `fn(test_string, parse_results)` and returns a string to be added to the test output
+ - ``file`` - (default= ``None``) optional file-like object to which test output will be written;
+ if None, will default to ``sys.stdout``
+ - ``with_line_numbers`` - default= ``False``) show test strings with line and column numbers
+
+ Returns: a (success, results) tuple, where success indicates that all tests succeeded
+ (or failed if ``failure_tests`` is True), and the results contain a list of lines of each
+ test's output
+
+ Example::
+
+ number_expr = pyparsing_common.number.copy()
+
+ result = number_expr.run_tests('''
+ # unsigned integer
+ 100
+ # negative integer
+ -100
+ # float with scientific notation
+ 6.02e23
+ # integer with scientific notation
+ 1e-12
+ ''')
+ print("Success" if result[0] else "Failed!")
+
+ result = number_expr.run_tests('''
+ # stray character
+ 100Z
+ # missing leading digit before '.'
+ -.100
+ # too many '.'
+ 3.14.159
+ ''', failure_tests=True)
+ print("Success" if result[0] else "Failed!")
+
+ prints::
+
+ # unsigned integer
+ 100
+ [100]
+
+ # negative integer
+ -100
+ [-100]
+
+ # float with scientific notation
+ 6.02e23
+ [6.02e+23]
+
+ # integer with scientific notation
+ 1e-12
+ [1e-12]
+
+ Success
+
+ # stray character
+ 100Z
+ ^
+ FAIL: Expected end of text (at char 3), (line:1, col:4)
+
+ # missing leading digit before '.'
+ -.100
+ ^
+ FAIL: Expected {real number with scientific notation | real number | signed integer} (at char 0), (line:1, col:1)
+
+ # too many '.'
+ 3.14.159
+ ^
+ FAIL: Expected end of text (at char 4), (line:1, col:5)
+
+ Success
+
+ Each test string must be on a single line. If you want to test a string that spans multiple
+ lines, create a test like this::
+
+ expr.run_tests(r"this is a test\\n of strings that spans \\n 3 lines")
+
+ (Note that this is a raw string literal, you must include the leading ``'r'``.)
+ """
+ from .testing import pyparsing_test
+
+ parseAll = parseAll and parse_all
+ fullDump = fullDump and full_dump
+ printResults = printResults and print_results
+ failureTests = failureTests or failure_tests
+ postParse = postParse or post_parse
+ if isinstance(tests, str_type):
+ line_strip = type(tests).strip
+ tests = [line_strip(test_line) for test_line in tests.rstrip().splitlines()]
+ if isinstance(comment, str_type):
+ comment = Literal(comment)
+ if file is None:
+ file = sys.stdout
+ print_ = file.write
+
+ result: Union[ParseResults, Exception]
+ allResults = []
+ comments = []
+ success = True
+ NL = Literal(r"\n").add_parse_action(replace_with("\n")).ignore(quoted_string)
+ BOM = "\ufeff"
+ for t in tests:
+ if comment is not None and comment.matches(t, False) or comments and not t:
+ comments.append(
+ pyparsing_test.with_line_numbers(t) if with_line_numbers else t
+ )
+ continue
+ if not t:
+ continue
+ out = [
+ "\n" + "\n".join(comments) if comments else "",
+ pyparsing_test.with_line_numbers(t) if with_line_numbers else t,
+ ]
+ comments = []
+ try:
+ # convert newline marks to actual newlines, and strip leading BOM if present
+ t = NL.transform_string(t.lstrip(BOM))
+ result = self.parse_string(t, parse_all=parseAll)
+ except ParseBaseException as pe:
+ fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else ""
+ out.append(pe.explain())
+ out.append("FAIL: " + str(pe))
+ if ParserElement.verbose_stacktrace:
+ out.extend(traceback.format_tb(pe.__traceback__))
+ success = success and failureTests
+ result = pe
+ except Exception as exc:
+ out.append("FAIL-EXCEPTION: {}: {}".format(type(exc).__name__, exc))
+ if ParserElement.verbose_stacktrace:
+ out.extend(traceback.format_tb(exc.__traceback__))
+ success = success and failureTests
+ result = exc
+ else:
+ success = success and not failureTests
+ if postParse is not None:
+ try:
+ pp_value = postParse(t, result)
+ if pp_value is not None:
+ if isinstance(pp_value, ParseResults):
+ out.append(pp_value.dump())
+ else:
+ out.append(str(pp_value))
+ else:
+ out.append(result.dump())
+ except Exception as e:
+ out.append(result.dump(full=fullDump))
+ out.append(
+ "{} failed: {}: {}".format(
+ postParse.__name__, type(e).__name__, e
+ )
+ )
+ else:
+ out.append(result.dump(full=fullDump))
+ out.append("")
+
+ if printResults:
+ print_("\n".join(out))
+
+ allResults.append((t, result))
+
+ return success, allResults
+
+ def create_diagram(
+ self,
+ output_html: Union[TextIO, Path, str],
+ vertical: int = 3,
+ show_results_names: bool = False,
+ show_groups: bool = False,
+ **kwargs,
+ ) -> None:
+ """
+ Create a railroad diagram for the parser.
+
+ Parameters:
+ - output_html (str or file-like object) - output target for generated
+ diagram HTML
+ - vertical (int) - threshold for formatting multiple alternatives vertically
+ instead of horizontally (default=3)
+ - show_results_names - bool flag whether diagram should show annotations for
+ defined results names
+ - show_groups - bool flag whether groups should be highlighted with an unlabeled surrounding box
+ Additional diagram-formatting keyword arguments can also be included;
+ see railroad.Diagram class.
+ """
+
+ try:
+ from .diagram import to_railroad, railroad_to_html
+ except ImportError as ie:
+ raise Exception(
+ "must ``pip install pyparsing[diagrams]`` to generate parser railroad diagrams"
+ ) from ie
+
+ self.streamline()
+
+ railroad = to_railroad(
+ self,
+ vertical=vertical,
+ show_results_names=show_results_names,
+ show_groups=show_groups,
+ diagram_kwargs=kwargs,
+ )
+ if isinstance(output_html, (str, Path)):
+ with open(output_html, "w", encoding="utf-8") as diag_file:
+ diag_file.write(railroad_to_html(railroad))
+ else:
+ # we were passed a file-like object, just write to it
+ output_html.write(railroad_to_html(railroad))
+
+ setDefaultWhitespaceChars = set_default_whitespace_chars
+ inlineLiteralsUsing = inline_literals_using
+ setResultsName = set_results_name
+ setBreak = set_break
+ setParseAction = set_parse_action
+ addParseAction = add_parse_action
+ addCondition = add_condition
+ setFailAction = set_fail_action
+ tryParse = try_parse
+ canParseNext = can_parse_next
+ resetCache = reset_cache
+ enableLeftRecursion = enable_left_recursion
+ enablePackrat = enable_packrat
+ parseString = parse_string
+ scanString = scan_string
+ searchString = search_string
+ transformString = transform_string
+ setWhitespaceChars = set_whitespace_chars
+ parseWithTabs = parse_with_tabs
+ setDebugActions = set_debug_actions
+ setDebug = set_debug
+ defaultName = default_name
+ setName = set_name
+ parseFile = parse_file
+ runTests = run_tests
+ ignoreWhitespace = ignore_whitespace
+ leaveWhitespace = leave_whitespace
+
+
+class _PendingSkip(ParserElement):
+ # internal placeholder class to hold a place were '...' is added to a parser element,
+ # once another ParserElement is added, this placeholder will be replaced with a SkipTo
+ def __init__(self, expr: ParserElement, must_skip: bool = False):
+ super().__init__()
+ self.anchor = expr
+ self.must_skip = must_skip
+
+ def _generateDefaultName(self):
+ return str(self.anchor + Empty()).replace("Empty", "...")
+
+ def __add__(self, other) -> "ParserElement":
+ skipper = SkipTo(other).set_name("...")("_skipped*")
+ if self.must_skip:
+
+ def must_skip(t):
+ if not t._skipped or t._skipped.as_list() == [""]:
+ del t[0]
+ t.pop("_skipped", None)
+
+ def show_skip(t):
+ if t._skipped.as_list()[-1:] == [""]:
+ t.pop("_skipped")
+ t["_skipped"] = "missing <" + repr(self.anchor) + ">"
+
+ return (
+ self.anchor + skipper().add_parse_action(must_skip)
+ | skipper().add_parse_action(show_skip)
+ ) + other
+
+ return self.anchor + skipper + other
+
+ def __repr__(self):
+ return self.defaultName
+
+ def parseImpl(self, *args):
+ raise Exception(
+ "use of `...` expression without following SkipTo target expression"
+ )
+
+
+class Token(ParserElement):
+ """Abstract :class:`ParserElement` subclass, for defining atomic
+ matching patterns.
+ """
+
+ def __init__(self):
+ super().__init__(savelist=False)
+
+ def _generateDefaultName(self):
+ return type(self).__name__
+
+
+class Empty(Token):
+ """
+ An empty token, will always match.
+ """
+
+ def __init__(self):
+ super().__init__()
+ self.mayReturnEmpty = True
+ self.mayIndexError = False
+
+
+class NoMatch(Token):
+ """
+ A token that will never match.
+ """
+
+ def __init__(self):
+ super().__init__()
+ self.mayReturnEmpty = True
+ self.mayIndexError = False
+ self.errmsg = "Unmatchable token"
+
+ def parseImpl(self, instring, loc, doActions=True):
+ raise ParseException(instring, loc, self.errmsg, self)
+
+
+class Literal(Token):
+ """
+ Token to exactly match a specified string.
+
+ Example::
+
+ Literal('blah').parse_string('blah') # -> ['blah']
+ Literal('blah').parse_string('blahfooblah') # -> ['blah']
+ Literal('blah').parse_string('bla') # -> Exception: Expected "blah"
+
+ For case-insensitive matching, use :class:`CaselessLiteral`.
+
+ For keyword matching (force word break before and after the matched string),
+ use :class:`Keyword` or :class:`CaselessKeyword`.
+ """
+
+ def __init__(self, match_string: str = "", *, matchString: str = ""):
+ super().__init__()
+ match_string = matchString or match_string
+ self.match = match_string
+ self.matchLen = len(match_string)
+ try:
+ self.firstMatchChar = match_string[0]
+ except IndexError:
+ raise ValueError("null string passed to Literal; use Empty() instead")
+ self.errmsg = "Expected " + self.name
+ self.mayReturnEmpty = False
+ self.mayIndexError = False
+
+ # Performance tuning: modify __class__ to select
+ # a parseImpl optimized for single-character check
+ if self.matchLen == 1 and type(self) is Literal:
+ self.__class__ = _SingleCharLiteral
+
+ def _generateDefaultName(self):
+ return repr(self.match)
+
+ def parseImpl(self, instring, loc, doActions=True):
+ if instring[loc] == self.firstMatchChar and instring.startswith(
+ self.match, loc
+ ):
+ return loc + self.matchLen, self.match
+ raise ParseException(instring, loc, self.errmsg, self)
+
+
+class _SingleCharLiteral(Literal):
+ def parseImpl(self, instring, loc, doActions=True):
+ if instring[loc] == self.firstMatchChar:
+ return loc + 1, self.match
+ raise ParseException(instring, loc, self.errmsg, self)
+
+
+ParserElement._literalStringClass = Literal
+
+
+class Keyword(Token):
+ """
+ Token to exactly match a specified string as a keyword, that is,
+ it must be immediately followed by a non-keyword character. Compare
+ with :class:`Literal`:
+
+ - ``Literal("if")`` will match the leading ``'if'`` in
+ ``'ifAndOnlyIf'``.
+ - ``Keyword("if")`` will not; it will only match the leading
+ ``'if'`` in ``'if x=1'``, or ``'if(y==2)'``
+
+ Accepts two optional constructor arguments in addition to the
+ keyword string:
+
+ - ``identChars`` is a string of characters that would be valid
+ identifier characters, defaulting to all alphanumerics + "_" and
+ "$"
+ - ``caseless`` allows case-insensitive matching, default is ``False``.
+
+ Example::
+
+ Keyword("start").parse_string("start") # -> ['start']
+ Keyword("start").parse_string("starting") # -> Exception
+
+ For case-insensitive matching, use :class:`CaselessKeyword`.
+ """
+
+ DEFAULT_KEYWORD_CHARS = alphanums + "_$"
+
+ def __init__(
+ self,
+ match_string: str = "",
+ ident_chars: typing.Optional[str] = None,
+ caseless: bool = False,
+ *,
+ matchString: str = "",
+ identChars: typing.Optional[str] = None,
+ ):
+ super().__init__()
+ identChars = identChars or ident_chars
+ if identChars is None:
+ identChars = Keyword.DEFAULT_KEYWORD_CHARS
+ match_string = matchString or match_string
+ self.match = match_string
+ self.matchLen = len(match_string)
+ try:
+ self.firstMatchChar = match_string[0]
+ except IndexError:
+ raise ValueError("null string passed to Keyword; use Empty() instead")
+ self.errmsg = "Expected {} {}".format(type(self).__name__, self.name)
+ self.mayReturnEmpty = False
+ self.mayIndexError = False
+ self.caseless = caseless
+ if caseless:
+ self.caselessmatch = match_string.upper()
+ identChars = identChars.upper()
+ self.identChars = set(identChars)
+
+ def _generateDefaultName(self):
+ return repr(self.match)
+
+ def parseImpl(self, instring, loc, doActions=True):
+ errmsg = self.errmsg
+ errloc = loc
+ if self.caseless:
+ if instring[loc : loc + self.matchLen].upper() == self.caselessmatch:
+ if loc == 0 or instring[loc - 1].upper() not in self.identChars:
+ if (
+ loc >= len(instring) - self.matchLen
+ or instring[loc + self.matchLen].upper() not in self.identChars
+ ):
+ return loc + self.matchLen, self.match
+ else:
+ # followed by keyword char
+ errmsg += ", was immediately followed by keyword character"
+ errloc = loc + self.matchLen
+ else:
+ # preceded by keyword char
+ errmsg += ", keyword was immediately preceded by keyword character"
+ errloc = loc - 1
+ # else no match just raise plain exception
+
+ else:
+ if (
+ instring[loc] == self.firstMatchChar
+ and self.matchLen == 1
+ or instring.startswith(self.match, loc)
+ ):
+ if loc == 0 or instring[loc - 1] not in self.identChars:
+ if (
+ loc >= len(instring) - self.matchLen
+ or instring[loc + self.matchLen] not in self.identChars
+ ):
+ return loc + self.matchLen, self.match
+ else:
+ # followed by keyword char
+ errmsg += (
+ ", keyword was immediately followed by keyword character"
+ )
+ errloc = loc + self.matchLen
+ else:
+ # preceded by keyword char
+ errmsg += ", keyword was immediately preceded by keyword character"
+ errloc = loc - 1
+ # else no match just raise plain exception
+
+ raise ParseException(instring, errloc, errmsg, self)
+
+ @staticmethod
+ def set_default_keyword_chars(chars) -> None:
+ """
+ Overrides the default characters used by :class:`Keyword` expressions.
+ """
+ Keyword.DEFAULT_KEYWORD_CHARS = chars
+
+ setDefaultKeywordChars = set_default_keyword_chars
+
+
+class CaselessLiteral(Literal):
+ """
+ Token to match a specified string, ignoring case of letters.
+ Note: the matched results will always be in the case of the given
+ match string, NOT the case of the input text.
+
+ Example::
+
+ CaselessLiteral("CMD")[1, ...].parse_string("cmd CMD Cmd10")
+ # -> ['CMD', 'CMD', 'CMD']
+
+ (Contrast with example for :class:`CaselessKeyword`.)
+ """
+
+ def __init__(self, match_string: str = "", *, matchString: str = ""):
+ match_string = matchString or match_string
+ super().__init__(match_string.upper())
+ # Preserve the defining literal.
+ self.returnString = match_string
+ self.errmsg = "Expected " + self.name
+
+ def parseImpl(self, instring, loc, doActions=True):
+ if instring[loc : loc + self.matchLen].upper() == self.match:
+ return loc + self.matchLen, self.returnString
+ raise ParseException(instring, loc, self.errmsg, self)
+
+
+class CaselessKeyword(Keyword):
+ """
+ Caseless version of :class:`Keyword`.
+
+ Example::
+
+ CaselessKeyword("CMD")[1, ...].parse_string("cmd CMD Cmd10")
+ # -> ['CMD', 'CMD']
+
+ (Contrast with example for :class:`CaselessLiteral`.)
+ """
+
+ def __init__(
+ self,
+ match_string: str = "",
+ ident_chars: typing.Optional[str] = None,
+ *,
+ matchString: str = "",
+ identChars: typing.Optional[str] = None,
+ ):
+ identChars = identChars or ident_chars
+ match_string = matchString or match_string
+ super().__init__(match_string, identChars, caseless=True)
+
+
+class CloseMatch(Token):
+ """A variation on :class:`Literal` which matches "close" matches,
+ that is, strings with at most 'n' mismatching characters.
+ :class:`CloseMatch` takes parameters:
+
+ - ``match_string`` - string to be matched
+ - ``caseless`` - a boolean indicating whether to ignore casing when comparing characters
+ - ``max_mismatches`` - (``default=1``) maximum number of
+ mismatches allowed to count as a match
+
+ The results from a successful parse will contain the matched text
+ from the input string and the following named results:
+
+ - ``mismatches`` - a list of the positions within the
+ match_string where mismatches were found
+ - ``original`` - the original match_string used to compare
+ against the input string
+
+ If ``mismatches`` is an empty list, then the match was an exact
+ match.
+
+ Example::
+
+ patt = CloseMatch("ATCATCGAATGGA")
+ patt.parse_string("ATCATCGAAXGGA") # -> (['ATCATCGAAXGGA'], {'mismatches': [[9]], 'original': ['ATCATCGAATGGA']})
+ patt.parse_string("ATCAXCGAAXGGA") # -> Exception: Expected 'ATCATCGAATGGA' (with up to 1 mismatches) (at char 0), (line:1, col:1)
+
+ # exact match
+ patt.parse_string("ATCATCGAATGGA") # -> (['ATCATCGAATGGA'], {'mismatches': [[]], 'original': ['ATCATCGAATGGA']})
+
+ # close match allowing up to 2 mismatches
+ patt = CloseMatch("ATCATCGAATGGA", max_mismatches=2)
+ patt.parse_string("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']})
+ """
+
+ def __init__(
+ self,
+ match_string: str,
+ max_mismatches: int = None,
+ *,
+ maxMismatches: int = 1,
+ caseless=False,
+ ):
+ maxMismatches = max_mismatches if max_mismatches is not None else maxMismatches
+ super().__init__()
+ self.match_string = match_string
+ self.maxMismatches = maxMismatches
+ self.errmsg = "Expected {!r} (with up to {} mismatches)".format(
+ self.match_string, self.maxMismatches
+ )
+ self.caseless = caseless
+ self.mayIndexError = False
+ self.mayReturnEmpty = False
+
+ def _generateDefaultName(self):
+ return "{}:{!r}".format(type(self).__name__, self.match_string)
+
+ def parseImpl(self, instring, loc, doActions=True):
+ start = loc
+ instrlen = len(instring)
+ maxloc = start + len(self.match_string)
+
+ if maxloc <= instrlen:
+ match_string = self.match_string
+ match_stringloc = 0
+ mismatches = []
+ maxMismatches = self.maxMismatches
+
+ for match_stringloc, s_m in enumerate(
+ zip(instring[loc:maxloc], match_string)
+ ):
+ src, mat = s_m
+ if self.caseless:
+ src, mat = src.lower(), mat.lower()
+
+ if src != mat:
+ mismatches.append(match_stringloc)
+ if len(mismatches) > maxMismatches:
+ break
+ else:
+ loc = start + match_stringloc + 1
+ results = ParseResults([instring[start:loc]])
+ results["original"] = match_string
+ results["mismatches"] = mismatches
+ return loc, results
+
+ raise ParseException(instring, loc, self.errmsg, self)
+
+
+class Word(Token):
+ """Token for matching words composed of allowed character sets.
+ Parameters:
+ - ``init_chars`` - string of all characters that should be used to
+ match as a word; "ABC" will match "AAA", "ABAB", "CBAC", etc.;
+ if ``body_chars`` is also specified, then this is the string of
+ initial characters
+ - ``body_chars`` - string of characters that
+ can be used for matching after a matched initial character as
+ given in ``init_chars``; if omitted, same as the initial characters
+ (default=``None``)
+ - ``min`` - minimum number of characters to match (default=1)
+ - ``max`` - maximum number of characters to match (default=0)
+ - ``exact`` - exact number of characters to match (default=0)
+ - ``as_keyword`` - match as a keyword (default=``False``)
+ - ``exclude_chars`` - characters that might be
+ found in the input ``body_chars`` string but which should not be
+ accepted for matching ;useful to define a word of all
+ printables except for one or two characters, for instance
+ (default=``None``)
+
+ :class:`srange` is useful for defining custom character set strings
+ for defining :class:`Word` expressions, using range notation from
+ regular expression character sets.
+
+ A common mistake is to use :class:`Word` to match a specific literal
+ string, as in ``Word("Address")``. Remember that :class:`Word`
+ uses the string argument to define *sets* of matchable characters.
+ This expression would match "Add", "AAA", "dAred", or any other word
+ made up of the characters 'A', 'd', 'r', 'e', and 's'. To match an
+ exact literal string, use :class:`Literal` or :class:`Keyword`.
+
+ pyparsing includes helper strings for building Words:
+
+ - :class:`alphas`
+ - :class:`nums`
+ - :class:`alphanums`
+ - :class:`hexnums`
+ - :class:`alphas8bit` (alphabetic characters in ASCII range 128-255
+ - accented, tilded, umlauted, etc.)
+ - :class:`punc8bit` (non-alphabetic characters in ASCII range
+ 128-255 - currency, symbols, superscripts, diacriticals, etc.)
+ - :class:`printables` (any non-whitespace character)
+
+ ``alphas``, ``nums``, and ``printables`` are also defined in several
+ Unicode sets - see :class:`pyparsing_unicode``.
+
+ Example::
+
+ # a word composed of digits
+ integer = Word(nums) # equivalent to Word("0123456789") or Word(srange("0-9"))
+
+ # a word with a leading capital, and zero or more lowercase
+ capital_word = Word(alphas.upper(), alphas.lower())
+
+ # hostnames are alphanumeric, with leading alpha, and '-'
+ hostname = Word(alphas, alphanums + '-')
+
+ # roman numeral (not a strict parser, accepts invalid mix of characters)
+ roman = Word("IVXLCDM")
+
+ # any string of non-whitespace characters, except for ','
+ csv_value = Word(printables, exclude_chars=",")
+ """
+
+ def __init__(
+ self,
+ init_chars: str = "",
+ body_chars: typing.Optional[str] = None,
+ min: int = 1,
+ max: int = 0,
+ exact: int = 0,
+ as_keyword: bool = False,
+ exclude_chars: typing.Optional[str] = None,
+ *,
+ initChars: typing.Optional[str] = None,
+ bodyChars: typing.Optional[str] = None,
+ asKeyword: bool = False,
+ excludeChars: typing.Optional[str] = None,
+ ):
+ initChars = initChars or init_chars
+ bodyChars = bodyChars or body_chars
+ asKeyword = asKeyword or as_keyword
+ excludeChars = excludeChars or exclude_chars
+ super().__init__()
+ if not initChars:
+ raise ValueError(
+ "invalid {}, initChars cannot be empty string".format(
+ type(self).__name__
+ )
+ )
+
+ initChars = set(initChars)
+ self.initChars = initChars
+ if excludeChars:
+ excludeChars = set(excludeChars)
+ initChars -= excludeChars
+ if bodyChars:
+ bodyChars = set(bodyChars) - excludeChars
+ self.initCharsOrig = "".join(sorted(initChars))
+
+ if bodyChars:
+ self.bodyCharsOrig = "".join(sorted(bodyChars))
+ self.bodyChars = set(bodyChars)
+ else:
+ self.bodyCharsOrig = "".join(sorted(initChars))
+ self.bodyChars = set(initChars)
+
+ self.maxSpecified = max > 0
+
+ if min < 1:
+ raise ValueError(
+ "cannot specify a minimum length < 1; use Opt(Word()) if zero-length word is permitted"
+ )
+
+ self.minLen = min
+
+ if max > 0:
+ self.maxLen = max
+ else:
+ self.maxLen = _MAX_INT
+
+ if exact > 0:
+ self.maxLen = exact
+ self.minLen = exact
+
+ self.errmsg = "Expected " + self.name
+ self.mayIndexError = False
+ self.asKeyword = asKeyword
+
+ # see if we can make a regex for this Word
+ if " " not in self.initChars | self.bodyChars and (min == 1 and exact == 0):
+ if self.bodyChars == self.initChars:
+ if max == 0:
+ repeat = "+"
+ elif max == 1:
+ repeat = ""
+ else:
+ repeat = "{{{},{}}}".format(
+ self.minLen, "" if self.maxLen == _MAX_INT else self.maxLen
+ )
+ self.reString = "[{}]{}".format(
+ _collapse_string_to_ranges(self.initChars),
+ repeat,
+ )
+ elif len(self.initChars) == 1:
+ if max == 0:
+ repeat = "*"
+ else:
+ repeat = "{{0,{}}}".format(max - 1)
+ self.reString = "{}[{}]{}".format(
+ re.escape(self.initCharsOrig),
+ _collapse_string_to_ranges(self.bodyChars),
+ repeat,
+ )
+ else:
+ if max == 0:
+ repeat = "*"
+ elif max == 2:
+ repeat = ""
+ else:
+ repeat = "{{0,{}}}".format(max - 1)
+ self.reString = "[{}][{}]{}".format(
+ _collapse_string_to_ranges(self.initChars),
+ _collapse_string_to_ranges(self.bodyChars),
+ repeat,
+ )
+ if self.asKeyword:
+ self.reString = r"\b" + self.reString + r"\b"
+
+ try:
+ self.re = re.compile(self.reString)
+ except re.error:
+ self.re = None
+ else:
+ self.re_match = self.re.match
+ self.__class__ = _WordRegex
+
+ def _generateDefaultName(self):
+ def charsAsStr(s):
+ max_repr_len = 16
+ s = _collapse_string_to_ranges(s, re_escape=False)
+ if len(s) > max_repr_len:
+ return s[: max_repr_len - 3] + "..."
+ else:
+ return s
+
+ if self.initChars != self.bodyChars:
+ base = "W:({}, {})".format(
+ charsAsStr(self.initChars), charsAsStr(self.bodyChars)
+ )
+ else:
+ base = "W:({})".format(charsAsStr(self.initChars))
+
+ # add length specification
+ if self.minLen > 1 or self.maxLen != _MAX_INT:
+ if self.minLen == self.maxLen:
+ if self.minLen == 1:
+ return base[2:]
+ else:
+ return base + "{{{}}}".format(self.minLen)
+ elif self.maxLen == _MAX_INT:
+ return base + "{{{},...}}".format(self.minLen)
+ else:
+ return base + "{{{},{}}}".format(self.minLen, self.maxLen)
+ return base
+
+ def parseImpl(self, instring, loc, doActions=True):
+ if instring[loc] not in self.initChars:
+ raise ParseException(instring, loc, self.errmsg, self)
+
+ start = loc
+ loc += 1
+ instrlen = len(instring)
+ bodychars = self.bodyChars
+ maxloc = start + self.maxLen
+ maxloc = min(maxloc, instrlen)
+ while loc < maxloc and instring[loc] in bodychars:
+ loc += 1
+
+ throwException = False
+ if loc - start < self.minLen:
+ throwException = True
+ elif self.maxSpecified and loc < instrlen and instring[loc] in bodychars:
+ throwException = True
+ elif self.asKeyword:
+ if (
+ start > 0
+ and instring[start - 1] in bodychars
+ or loc < instrlen
+ and instring[loc] in bodychars
+ ):
+ throwException = True
+
+ if throwException:
+ raise ParseException(instring, loc, self.errmsg, self)
+
+ return loc, instring[start:loc]
+
+
+class _WordRegex(Word):
+ def parseImpl(self, instring, loc, doActions=True):
+ result = self.re_match(instring, loc)
+ if not result:
+ raise ParseException(instring, loc, self.errmsg, self)
+
+ loc = result.end()
+ return loc, result.group()
+
+
+class Char(_WordRegex):
+ """A short-cut class for defining :class:`Word` ``(characters, exact=1)``,
+ when defining a match of any single character in a string of
+ characters.
+ """
+
+ def __init__(
+ self,
+ charset: str,
+ as_keyword: bool = False,
+ exclude_chars: typing.Optional[str] = None,
+ *,
+ asKeyword: bool = False,
+ excludeChars: typing.Optional[str] = None,
+ ):
+ asKeyword = asKeyword or as_keyword
+ excludeChars = excludeChars or exclude_chars
+ super().__init__(
+ charset, exact=1, asKeyword=asKeyword, excludeChars=excludeChars
+ )
+ self.reString = "[{}]".format(_collapse_string_to_ranges(self.initChars))
+ if asKeyword:
+ self.reString = r"\b{}\b".format(self.reString)
+ self.re = re.compile(self.reString)
+ self.re_match = self.re.match
+
+
+class Regex(Token):
+ r"""Token for matching strings that match a given regular
+ expression. Defined with string specifying the regular expression in
+ a form recognized by the stdlib Python `re module <https://docs.python.org/3/library/re.html>`_.
+ If the given regex contains named groups (defined using ``(?P<name>...)``),
+ these will be preserved as named :class:`ParseResults`.
+
+ If instead of the Python stdlib ``re`` module you wish to use a different RE module
+ (such as the ``regex`` module), you can do so by building your ``Regex`` object with
+ a compiled RE that was compiled using ``regex``.
+
+ Example::
+
+ realnum = Regex(r"[+-]?\d+\.\d*")
+ # ref: https://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression
+ roman = Regex(r"M{0,4}(CM|CD|D?{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})")
+
+ # named fields in a regex will be returned as named results
+ date = Regex(r'(?P<year>\d{4})-(?P<month>\d\d?)-(?P<day>\d\d?)')
+
+ # the Regex class will accept re's compiled using the regex module
+ import regex
+ parser = pp.Regex(regex.compile(r'[0-9]'))
+ """
+
+ def __init__(
+ self,
+ pattern: Any,
+ flags: Union[re.RegexFlag, int] = 0,
+ as_group_list: bool = False,
+ as_match: bool = False,
+ *,
+ asGroupList: bool = False,
+ asMatch: bool = False,
+ ):
+ """The parameters ``pattern`` and ``flags`` are passed
+ to the ``re.compile()`` function as-is. See the Python
+ `re module <https://docs.python.org/3/library/re.html>`_ module for an
+ explanation of the acceptable patterns and flags.
+ """
+ super().__init__()
+ asGroupList = asGroupList or as_group_list
+ asMatch = asMatch or as_match
+
+ if isinstance(pattern, str_type):
+ if not pattern:
+ raise ValueError("null string passed to Regex; use Empty() instead")
+
+ self._re = None
+ self.reString = self.pattern = pattern
+ self.flags = flags
+
+ elif hasattr(pattern, "pattern") and hasattr(pattern, "match"):
+ self._re = pattern
+ self.pattern = self.reString = pattern.pattern
+ self.flags = flags
+
+ else:
+ raise TypeError(
+ "Regex may only be constructed with a string or a compiled RE object"
+ )
+
+ self.errmsg = "Expected " + self.name
+ self.mayIndexError = False
+ self.asGroupList = asGroupList
+ self.asMatch = asMatch
+ if self.asGroupList:
+ self.parseImpl = self.parseImplAsGroupList
+ if self.asMatch:
+ self.parseImpl = self.parseImplAsMatch
+
+ @cached_property
+ def re(self):
+ if self._re:
+ return self._re
+ else:
+ try:
+ return re.compile(self.pattern, self.flags)
+ except re.error:
+ raise ValueError(
+ "invalid pattern ({!r}) passed to Regex".format(self.pattern)
+ )
+
+ @cached_property
+ def re_match(self):
+ return self.re.match
+
+ @cached_property
+ def mayReturnEmpty(self):
+ return self.re_match("") is not None
+
+ def _generateDefaultName(self):
+ return "Re:({})".format(repr(self.pattern).replace("\\\\", "\\"))
+
+ def parseImpl(self, instring, loc, doActions=True):
+ result = self.re_match(instring, loc)
+ if not result:
+ raise ParseException(instring, loc, self.errmsg, self)
+
+ loc = result.end()
+ ret = ParseResults(result.group())
+ d = result.groupdict()
+ if d:
+ for k, v in d.items():
+ ret[k] = v
+ return loc, ret
+
+ def parseImplAsGroupList(self, instring, loc, doActions=True):
+ result = self.re_match(instring, loc)
+ if not result:
+ raise ParseException(instring, loc, self.errmsg, self)
+
+ loc = result.end()
+ ret = result.groups()
+ return loc, ret
+
+ def parseImplAsMatch(self, instring, loc, doActions=True):
+ result = self.re_match(instring, loc)
+ if not result:
+ raise ParseException(instring, loc, self.errmsg, self)
+
+ loc = result.end()
+ ret = result
+ return loc, ret
+
+ def sub(self, repl: str) -> ParserElement:
+ r"""
+ Return :class:`Regex` with an attached parse action to transform the parsed
+ result as if called using `re.sub(expr, repl, string) <https://docs.python.org/3/library/re.html#re.sub>`_.
+
+ Example::
+
+ make_html = Regex(r"(\w+):(.*?):").sub(r"<\1>\2</\1>")
+ print(make_html.transform_string("h1:main title:"))
+ # prints "<h1>main title</h1>"
+ """
+ if self.asGroupList:
+ raise TypeError("cannot use sub() with Regex(asGroupList=True)")
+
+ if self.asMatch and callable(repl):
+ raise TypeError("cannot use sub() with a callable with Regex(asMatch=True)")
+
+ if self.asMatch:
+
+ def pa(tokens):
+ return tokens[0].expand(repl)
+
+ else:
+
+ def pa(tokens):
+ return self.re.sub(repl, tokens[0])
+
+ return self.add_parse_action(pa)
+
+
+class QuotedString(Token):
+ r"""
+ Token for matching strings that are delimited by quoting characters.
+
+ Defined with the following parameters:
+
+ - ``quote_char`` - string of one or more characters defining the
+ quote delimiting string
+ - ``esc_char`` - character to re_escape quotes, typically backslash
+ (default= ``None``)
+ - ``esc_quote`` - special quote sequence to re_escape an embedded quote
+ string (such as SQL's ``""`` to re_escape an embedded ``"``)
+ (default= ``None``)
+ - ``multiline`` - boolean indicating whether quotes can span
+ multiple lines (default= ``False``)
+ - ``unquote_results`` - boolean indicating whether the matched text
+ should be unquoted (default= ``True``)
+ - ``end_quote_char`` - string of one or more characters defining the
+ end of the quote delimited string (default= ``None`` => same as
+ quote_char)
+ - ``convert_whitespace_escapes`` - convert escaped whitespace
+ (``'\t'``, ``'\n'``, etc.) to actual whitespace
+ (default= ``True``)
+
+ Example::
+
+ qs = QuotedString('"')
+ print(qs.search_string('lsjdf "This is the quote" sldjf'))
+ complex_qs = QuotedString('{{', end_quote_char='}}')
+ print(complex_qs.search_string('lsjdf {{This is the "quote"}} sldjf'))
+ sql_qs = QuotedString('"', esc_quote='""')
+ print(sql_qs.search_string('lsjdf "This is the quote with ""embedded"" quotes" sldjf'))
+
+ prints::
+
+ [['This is the quote']]
+ [['This is the "quote"']]
+ [['This is the quote with "embedded" quotes']]
+ """
+ ws_map = ((r"\t", "\t"), (r"\n", "\n"), (r"\f", "\f"), (r"\r", "\r"))
+
+ def __init__(
+ self,
+ quote_char: str = "",
+ esc_char: typing.Optional[str] = None,
+ esc_quote: typing.Optional[str] = None,
+ multiline: bool = False,
+ unquote_results: bool = True,
+ end_quote_char: typing.Optional[str] = None,
+ convert_whitespace_escapes: bool = True,
+ *,
+ quoteChar: str = "",
+ escChar: typing.Optional[str] = None,
+ escQuote: typing.Optional[str] = None,
+ unquoteResults: bool = True,
+ endQuoteChar: typing.Optional[str] = None,
+ convertWhitespaceEscapes: bool = True,
+ ):
+ super().__init__()
+ escChar = escChar or esc_char
+ escQuote = escQuote or esc_quote
+ unquoteResults = unquoteResults and unquote_results
+ endQuoteChar = endQuoteChar or end_quote_char
+ convertWhitespaceEscapes = (
+ convertWhitespaceEscapes and convert_whitespace_escapes
+ )
+ quote_char = quoteChar or quote_char
+
+ # remove white space from quote chars - wont work anyway
+ quote_char = quote_char.strip()
+ if not quote_char:
+ raise ValueError("quote_char cannot be the empty string")
+
+ if endQuoteChar is None:
+ endQuoteChar = quote_char
+ else:
+ endQuoteChar = endQuoteChar.strip()
+ if not endQuoteChar:
+ raise ValueError("endQuoteChar cannot be the empty string")
+
+ self.quoteChar = quote_char
+ self.quoteCharLen = len(quote_char)
+ self.firstQuoteChar = quote_char[0]
+ self.endQuoteChar = endQuoteChar
+ self.endQuoteCharLen = len(endQuoteChar)
+ self.escChar = escChar
+ self.escQuote = escQuote
+ self.unquoteResults = unquoteResults
+ self.convertWhitespaceEscapes = convertWhitespaceEscapes
+
+ sep = ""
+ inner_pattern = ""
+
+ if escQuote:
+ inner_pattern += r"{}(?:{})".format(sep, re.escape(escQuote))
+ sep = "|"
+
+ if escChar:
+ inner_pattern += r"{}(?:{}.)".format(sep, re.escape(escChar))
+ sep = "|"
+ self.escCharReplacePattern = re.escape(self.escChar) + "(.)"
+
+ if len(self.endQuoteChar) > 1:
+ inner_pattern += (
+ "{}(?:".format(sep)
+ + "|".join(
+ "(?:{}(?!{}))".format(
+ re.escape(self.endQuoteChar[:i]),
+ re.escape(self.endQuoteChar[i:]),
+ )
+ for i in range(len(self.endQuoteChar) - 1, 0, -1)
+ )
+ + ")"
+ )
+ sep = "|"
+
+ if multiline:
+ self.flags = re.MULTILINE | re.DOTALL
+ inner_pattern += r"{}(?:[^{}{}])".format(
+ sep,
+ _escape_regex_range_chars(self.endQuoteChar[0]),
+ (_escape_regex_range_chars(escChar) if escChar is not None else ""),
+ )
+ else:
+ self.flags = 0
+ inner_pattern += r"{}(?:[^{}\n\r{}])".format(
+ sep,
+ _escape_regex_range_chars(self.endQuoteChar[0]),
+ (_escape_regex_range_chars(escChar) if escChar is not None else ""),
+ )
+
+ self.pattern = "".join(
+ [
+ re.escape(self.quoteChar),
+ "(?:",
+ inner_pattern,
+ ")*",
+ re.escape(self.endQuoteChar),
+ ]
+ )
+
+ try:
+ self.re = re.compile(self.pattern, self.flags)
+ self.reString = self.pattern
+ self.re_match = self.re.match
+ except re.error:
+ raise ValueError(
+ "invalid pattern {!r} passed to Regex".format(self.pattern)
+ )
+
+ self.errmsg = "Expected " + self.name
+ self.mayIndexError = False
+ self.mayReturnEmpty = True
+
+ def _generateDefaultName(self):
+ if self.quoteChar == self.endQuoteChar and isinstance(self.quoteChar, str_type):
+ return "string enclosed in {!r}".format(self.quoteChar)
+
+ return "quoted string, starting with {} ending with {}".format(
+ self.quoteChar, self.endQuoteChar
+ )
+
+ def parseImpl(self, instring, loc, doActions=True):
+ result = (
+ instring[loc] == self.firstQuoteChar
+ and self.re_match(instring, loc)
+ or None
+ )
+ if not result:
+ raise ParseException(instring, loc, self.errmsg, self)
+
+ loc = result.end()
+ ret = result.group()
+
+ if self.unquoteResults:
+
+ # strip off quotes
+ ret = ret[self.quoteCharLen : -self.endQuoteCharLen]
+
+ if isinstance(ret, str_type):
+ # replace escaped whitespace
+ if "\\" in ret and self.convertWhitespaceEscapes:
+ for wslit, wschar in self.ws_map:
+ ret = ret.replace(wslit, wschar)
+
+ # replace escaped characters
+ if self.escChar:
+ ret = re.sub(self.escCharReplacePattern, r"\g<1>", ret)
+
+ # replace escaped quotes
+ if self.escQuote:
+ ret = ret.replace(self.escQuote, self.endQuoteChar)
+
+ return loc, ret
+
+
+class CharsNotIn(Token):
+ """Token for matching words composed of characters *not* in a given
+ set (will include whitespace in matched characters if not listed in
+ the provided exclusion set - see example). Defined with string
+ containing all disallowed characters, and an optional minimum,
+ maximum, and/or exact length. The default value for ``min`` is
+ 1 (a minimum value < 1 is not valid); the default values for
+ ``max`` and ``exact`` are 0, meaning no maximum or exact
+ length restriction.
+
+ Example::
+
+ # define a comma-separated-value as anything that is not a ','
+ csv_value = CharsNotIn(',')
+ print(delimited_list(csv_value).parse_string("dkls,lsdkjf,s12 34,@!#,213"))
+
+ prints::
+
+ ['dkls', 'lsdkjf', 's12 34', '@!#', '213']
+ """
+
+ def __init__(
+ self,
+ not_chars: str = "",
+ min: int = 1,
+ max: int = 0,
+ exact: int = 0,
+ *,
+ notChars: str = "",
+ ):
+ super().__init__()
+ self.skipWhitespace = False
+ self.notChars = not_chars or notChars
+ self.notCharsSet = set(self.notChars)
+
+ if min < 1:
+ raise ValueError(
+ "cannot specify a minimum length < 1; use "
+ "Opt(CharsNotIn()) if zero-length char group is permitted"
+ )
+
+ self.minLen = min
+
+ if max > 0:
+ self.maxLen = max
+ else:
+ self.maxLen = _MAX_INT
+
+ if exact > 0:
+ self.maxLen = exact
+ self.minLen = exact
+
+ self.errmsg = "Expected " + self.name
+ self.mayReturnEmpty = self.minLen == 0
+ self.mayIndexError = False
+
+ def _generateDefaultName(self):
+ not_chars_str = _collapse_string_to_ranges(self.notChars)
+ if len(not_chars_str) > 16:
+ return "!W:({}...)".format(self.notChars[: 16 - 3])
+ else:
+ return "!W:({})".format(self.notChars)
+
+ def parseImpl(self, instring, loc, doActions=True):
+ notchars = self.notCharsSet
+ if instring[loc] in notchars:
+ raise ParseException(instring, loc, self.errmsg, self)
+
+ start = loc
+ loc += 1
+ maxlen = min(start + self.maxLen, len(instring))
+ while loc < maxlen and instring[loc] not in notchars:
+ loc += 1
+
+ if loc - start < self.minLen:
+ raise ParseException(instring, loc, self.errmsg, self)
+
+ return loc, instring[start:loc]
+
+
+class White(Token):
+ """Special matching class for matching whitespace. Normally,
+ whitespace is ignored by pyparsing grammars. This class is included
+ when some whitespace structures are significant. Define with
+ a string containing the whitespace characters to be matched; default
+ is ``" \\t\\r\\n"``. Also takes optional ``min``,
+ ``max``, and ``exact`` arguments, as defined for the
+ :class:`Word` class.
+ """
+
+ whiteStrs = {
+ " ": "<SP>",
+ "\t": "<TAB>",
+ "\n": "<LF>",
+ "\r": "<CR>",
+ "\f": "<FF>",
+ "\u00A0": "<NBSP>",
+ "\u1680": "<OGHAM_SPACE_MARK>",
+ "\u180E": "<MONGOLIAN_VOWEL_SEPARATOR>",
+ "\u2000": "<EN_QUAD>",
+ "\u2001": "<EM_QUAD>",
+ "\u2002": "<EN_SPACE>",
+ "\u2003": "<EM_SPACE>",
+ "\u2004": "<THREE-PER-EM_SPACE>",
+ "\u2005": "<FOUR-PER-EM_SPACE>",
+ "\u2006": "<SIX-PER-EM_SPACE>",
+ "\u2007": "<FIGURE_SPACE>",
+ "\u2008": "<PUNCTUATION_SPACE>",
+ "\u2009": "<THIN_SPACE>",
+ "\u200A": "<HAIR_SPACE>",
+ "\u200B": "<ZERO_WIDTH_SPACE>",
+ "\u202F": "<NNBSP>",
+ "\u205F": "<MMSP>",
+ "\u3000": "<IDEOGRAPHIC_SPACE>",
+ }
+
+ def __init__(self, ws: str = " \t\r\n", min: int = 1, max: int = 0, exact: int = 0):
+ super().__init__()
+ self.matchWhite = ws
+ self.set_whitespace_chars(
+ "".join(c for c in self.whiteStrs if c not in self.matchWhite),
+ copy_defaults=True,
+ )
+ # self.leave_whitespace()
+ self.mayReturnEmpty = True
+ self.errmsg = "Expected " + self.name
+
+ self.minLen = min
+
+ if max > 0:
+ self.maxLen = max
+ else:
+ self.maxLen = _MAX_INT
+
+ if exact > 0:
+ self.maxLen = exact
+ self.minLen = exact
+
+ def _generateDefaultName(self):
+ return "".join(White.whiteStrs[c] for c in self.matchWhite)
+
+ def parseImpl(self, instring, loc, doActions=True):
+ if instring[loc] not in self.matchWhite:
+ raise ParseException(instring, loc, self.errmsg, self)
+ start = loc
+ loc += 1
+ maxloc = start + self.maxLen
+ maxloc = min(maxloc, len(instring))
+ while loc < maxloc and instring[loc] in self.matchWhite:
+ loc += 1
+
+ if loc - start < self.minLen:
+ raise ParseException(instring, loc, self.errmsg, self)
+
+ return loc, instring[start:loc]
+
+
+class PositionToken(Token):
+ def __init__(self):
+ super().__init__()
+ self.mayReturnEmpty = True
+ self.mayIndexError = False
+
+
+class GoToColumn(PositionToken):
+ """Token to advance to a specific column of input text; useful for
+ tabular report scraping.
+ """
+
+ def __init__(self, colno: int):
+ super().__init__()
+ self.col = colno
+
+ def preParse(self, instring, loc):
+ if col(loc, instring) != self.col:
+ instrlen = len(instring)
+ if self.ignoreExprs:
+ loc = self._skipIgnorables(instring, loc)
+ while (
+ loc < instrlen
+ and instring[loc].isspace()
+ and col(loc, instring) != self.col
+ ):
+ loc += 1
+ return loc
+
+ def parseImpl(self, instring, loc, doActions=True):
+ thiscol = col(loc, instring)
+ if thiscol > self.col:
+ raise ParseException(instring, loc, "Text not in expected column", self)
+ newloc = loc + self.col - thiscol
+ ret = instring[loc:newloc]
+ return newloc, ret
+
+
+class LineStart(PositionToken):
+ r"""Matches if current position is at the beginning of a line within
+ the parse string
+
+ Example::
+
+ test = '''\
+ AAA this line
+ AAA and this line
+ AAA but not this one
+ B AAA and definitely not this one
+ '''
+
+ for t in (LineStart() + 'AAA' + restOfLine).search_string(test):
+ print(t)
+
+ prints::
+
+ ['AAA', ' this line']
+ ['AAA', ' and this line']
+
+ """
+
+ def __init__(self):
+ super().__init__()
+ self.leave_whitespace()
+ self.orig_whiteChars = set() | self.whiteChars
+ self.whiteChars.discard("\n")
+ self.skipper = Empty().set_whitespace_chars(self.whiteChars)
+ self.errmsg = "Expected start of line"
+
+ def preParse(self, instring, loc):
+ if loc == 0:
+ return loc
+ else:
+ ret = self.skipper.preParse(instring, loc)
+ if "\n" in self.orig_whiteChars:
+ while instring[ret : ret + 1] == "\n":
+ ret = self.skipper.preParse(instring, ret + 1)
+ return ret
+
+ def parseImpl(self, instring, loc, doActions=True):
+ if col(loc, instring) == 1:
+ return loc, []
+ raise ParseException(instring, loc, self.errmsg, self)
+
+
+class LineEnd(PositionToken):
+ """Matches if current position is at the end of a line within the
+ parse string
+ """
+
+ def __init__(self):
+ super().__init__()
+ self.whiteChars.discard("\n")
+ self.set_whitespace_chars(self.whiteChars, copy_defaults=False)
+ self.errmsg = "Expected end of line"
+
+ def parseImpl(self, instring, loc, doActions=True):
+ if loc < len(instring):
+ if instring[loc] == "\n":
+ return loc + 1, "\n"
+ else:
+ raise ParseException(instring, loc, self.errmsg, self)
+ elif loc == len(instring):
+ return loc + 1, []
+ else:
+ raise ParseException(instring, loc, self.errmsg, self)
+
+
+class StringStart(PositionToken):
+ """Matches if current position is at the beginning of the parse
+ string
+ """
+
+ def __init__(self):
+ super().__init__()
+ self.errmsg = "Expected start of text"
+
+ def parseImpl(self, instring, loc, doActions=True):
+ if loc != 0:
+ # see if entire string up to here is just whitespace and ignoreables
+ if loc != self.preParse(instring, 0):
+ raise ParseException(instring, loc, self.errmsg, self)
+ return loc, []
+
+
+class StringEnd(PositionToken):
+ """
+ Matches if current position is at the end of the parse string
+ """
+
+ def __init__(self):
+ super().__init__()
+ self.errmsg = "Expected end of text"
+
+ def parseImpl(self, instring, loc, doActions=True):
+ if loc < len(instring):
+ raise ParseException(instring, loc, self.errmsg, self)
+ elif loc == len(instring):
+ return loc + 1, []
+ elif loc > len(instring):
+ return loc, []
+ else:
+ raise ParseException(instring, loc, self.errmsg, self)
+
+
+class WordStart(PositionToken):
+ """Matches if the current position is at the beginning of a
+ :class:`Word`, and is not preceded by any character in a given
+ set of ``word_chars`` (default= ``printables``). To emulate the
+ ``\b`` behavior of regular expressions, use
+ ``WordStart(alphanums)``. ``WordStart`` will also match at
+ the beginning of the string being parsed, or at the beginning of
+ a line.
+ """
+
+ def __init__(self, word_chars: str = printables, *, wordChars: str = printables):
+ wordChars = word_chars if wordChars == printables else wordChars
+ super().__init__()
+ self.wordChars = set(wordChars)
+ self.errmsg = "Not at the start of a word"
+
+ def parseImpl(self, instring, loc, doActions=True):
+ if loc != 0:
+ if (
+ instring[loc - 1] in self.wordChars
+ or instring[loc] not in self.wordChars
+ ):
+ raise ParseException(instring, loc, self.errmsg, self)
+ return loc, []
+
+
+class WordEnd(PositionToken):
+ """Matches if the current position is at the end of a :class:`Word`,
+ and is not followed by any character in a given set of ``word_chars``
+ (default= ``printables``). To emulate the ``\b`` behavior of
+ regular expressions, use ``WordEnd(alphanums)``. ``WordEnd``
+ will also match at the end of the string being parsed, or at the end
+ of a line.
+ """
+
+ def __init__(self, word_chars: str = printables, *, wordChars: str = printables):
+ wordChars = word_chars if wordChars == printables else wordChars
+ super().__init__()
+ self.wordChars = set(wordChars)
+ self.skipWhitespace = False
+ self.errmsg = "Not at the end of a word"
+
+ def parseImpl(self, instring, loc, doActions=True):
+ instrlen = len(instring)
+ if instrlen > 0 and loc < instrlen:
+ if (
+ instring[loc] in self.wordChars
+ or instring[loc - 1] not in self.wordChars
+ ):
+ raise ParseException(instring, loc, self.errmsg, self)
+ return loc, []
+
+
+class ParseExpression(ParserElement):
+ """Abstract subclass of ParserElement, for combining and
+ post-processing parsed tokens.
+ """
+
+ def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
+ super().__init__(savelist)
+ self.exprs: List[ParserElement]
+ if isinstance(exprs, _generatorType):
+ exprs = list(exprs)
+
+ if isinstance(exprs, str_type):
+ self.exprs = [self._literalStringClass(exprs)]
+ elif isinstance(exprs, ParserElement):
+ self.exprs = [exprs]
+ elif isinstance(exprs, Iterable):
+ exprs = list(exprs)
+ # if sequence of strings provided, wrap with Literal
+ if any(isinstance(expr, str_type) for expr in exprs):
+ exprs = (
+ self._literalStringClass(e) if isinstance(e, str_type) else e
+ for e in exprs
+ )
+ self.exprs = list(exprs)
+ else:
+ try:
+ self.exprs = list(exprs)
+ except TypeError:
+ self.exprs = [exprs]
+ self.callPreparse = False
+
+ def recurse(self) -> Sequence[ParserElement]:
+ return self.exprs[:]
+
+ def append(self, other) -> ParserElement:
+ self.exprs.append(other)
+ self._defaultName = None
+ return self
+
+ def leave_whitespace(self, recursive: bool = True) -> ParserElement:
+ """
+ Extends ``leave_whitespace`` defined in base class, and also invokes ``leave_whitespace`` on
+ all contained expressions.
+ """
+ super().leave_whitespace(recursive)
+
+ if recursive:
+ self.exprs = [e.copy() for e in self.exprs]
+ for e in self.exprs:
+ e.leave_whitespace(recursive)
+ return self
+
+ def ignore_whitespace(self, recursive: bool = True) -> ParserElement:
+ """
+ Extends ``ignore_whitespace`` defined in base class, and also invokes ``leave_whitespace`` on
+ all contained expressions.
+ """
+ super().ignore_whitespace(recursive)
+ if recursive:
+ self.exprs = [e.copy() for e in self.exprs]
+ for e in self.exprs:
+ e.ignore_whitespace(recursive)
+ return self
+
+ def ignore(self, other) -> ParserElement:
+ if isinstance(other, Suppress):
+ if other not in self.ignoreExprs:
+ super().ignore(other)
+ for e in self.exprs:
+ e.ignore(self.ignoreExprs[-1])
+ else:
+ super().ignore(other)
+ for e in self.exprs:
+ e.ignore(self.ignoreExprs[-1])
+ return self
+
+ def _generateDefaultName(self):
+ return "{}:({})".format(self.__class__.__name__, str(self.exprs))
+
+ def streamline(self) -> ParserElement:
+ if self.streamlined:
+ return self
+
+ super().streamline()
+
+ for e in self.exprs:
+ e.streamline()
+
+ # collapse nested :class:`And`'s of the form ``And(And(And(a, b), c), d)`` to ``And(a, b, c, d)``
+ # but only if there are no parse actions or resultsNames on the nested And's
+ # (likewise for :class:`Or`'s and :class:`MatchFirst`'s)
+ if len(self.exprs) == 2:
+ other = self.exprs[0]
+ if (
+ isinstance(other, self.__class__)
+ and not other.parseAction
+ and other.resultsName is None
+ and not other.debug
+ ):
+ self.exprs = other.exprs[:] + [self.exprs[1]]
+ self._defaultName = None
+ self.mayReturnEmpty |= other.mayReturnEmpty
+ self.mayIndexError |= other.mayIndexError
+
+ other = self.exprs[-1]
+ if (
+ isinstance(other, self.__class__)
+ and not other.parseAction
+ and other.resultsName is None
+ and not other.debug
+ ):
+ self.exprs = self.exprs[:-1] + other.exprs[:]
+ self._defaultName = None
+ self.mayReturnEmpty |= other.mayReturnEmpty
+ self.mayIndexError |= other.mayIndexError
+
+ self.errmsg = "Expected " + str(self)
+
+ return self
+
+ def validate(self, validateTrace=None) -> None:
+ tmp = (validateTrace if validateTrace is not None else [])[:] + [self]
+ for e in self.exprs:
+ e.validate(tmp)
+ self._checkRecursion([])
+
+ def copy(self) -> ParserElement:
+ ret = super().copy()
+ ret.exprs = [e.copy() for e in self.exprs]
+ return ret
+
+ def _setResultsName(self, name, listAllMatches=False):
+ if (
+ __diag__.warn_ungrouped_named_tokens_in_collection
+ and Diagnostics.warn_ungrouped_named_tokens_in_collection
+ not in self.suppress_warnings_
+ ):
+ for e in self.exprs:
+ if (
+ isinstance(e, ParserElement)
+ and e.resultsName
+ and Diagnostics.warn_ungrouped_named_tokens_in_collection
+ not in e.suppress_warnings_
+ ):
+ warnings.warn(
+ "{}: setting results name {!r} on {} expression "
+ "collides with {!r} on contained expression".format(
+ "warn_ungrouped_named_tokens_in_collection",
+ name,
+ type(self).__name__,
+ e.resultsName,
+ ),
+ stacklevel=3,
+ )
+
+ return super()._setResultsName(name, listAllMatches)
+
+ ignoreWhitespace = ignore_whitespace
+ leaveWhitespace = leave_whitespace
+
+
+class And(ParseExpression):
+ """
+ Requires all given :class:`ParseExpression` s to be found in the given order.
+ Expressions may be separated by whitespace.
+ May be constructed using the ``'+'`` operator.
+ May also be constructed using the ``'-'`` operator, which will
+ suppress backtracking.
+
+ Example::
+
+ integer = Word(nums)
+ name_expr = Word(alphas)[1, ...]
+
+ expr = And([integer("id"), name_expr("name"), integer("age")])
+ # more easily written as:
+ expr = integer("id") + name_expr("name") + integer("age")
+ """
+
+ class _ErrorStop(Empty):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.leave_whitespace()
+
+ def _generateDefaultName(self):
+ return "-"
+
+ def __init__(
+ self, exprs_arg: typing.Iterable[ParserElement], savelist: bool = True
+ ):
+ exprs: List[ParserElement] = list(exprs_arg)
+ if exprs and Ellipsis in exprs:
+ tmp = []
+ for i, expr in enumerate(exprs):
+ if expr is Ellipsis:
+ if i < len(exprs) - 1:
+ skipto_arg: ParserElement = (Empty() + exprs[i + 1]).exprs[-1]
+ tmp.append(SkipTo(skipto_arg)("_skipped*"))
+ else:
+ raise Exception(
+ "cannot construct And with sequence ending in ..."
+ )
+ else:
+ tmp.append(expr)
+ exprs[:] = tmp
+ super().__init__(exprs, savelist)
+ if self.exprs:
+ self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
+ if not isinstance(self.exprs[0], White):
+ self.set_whitespace_chars(
+ self.exprs[0].whiteChars,
+ copy_defaults=self.exprs[0].copyDefaultWhiteChars,
+ )
+ self.skipWhitespace = self.exprs[0].skipWhitespace
+ else:
+ self.skipWhitespace = False
+ else:
+ self.mayReturnEmpty = True
+ self.callPreparse = True
+
+ def streamline(self) -> ParserElement:
+ # collapse any _PendingSkip's
+ if self.exprs:
+ if any(
+ isinstance(e, ParseExpression)
+ and e.exprs
+ and isinstance(e.exprs[-1], _PendingSkip)
+ for e in self.exprs[:-1]
+ ):
+ for i, e in enumerate(self.exprs[:-1]):
+ if e is None:
+ continue
+ if (
+ isinstance(e, ParseExpression)
+ and e.exprs
+ and isinstance(e.exprs[-1], _PendingSkip)
+ ):
+ e.exprs[-1] = e.exprs[-1] + self.exprs[i + 1]
+ self.exprs[i + 1] = None
+ self.exprs = [e for e in self.exprs if e is not None]
+
+ super().streamline()
+
+ # link any IndentedBlocks to the prior expression
+ for prev, cur in zip(self.exprs, self.exprs[1:]):
+ # traverse cur or any first embedded expr of cur looking for an IndentedBlock
+ # (but watch out for recursive grammar)
+ seen = set()
+ while cur:
+ if id(cur) in seen:
+ break
+ seen.add(id(cur))
+ if isinstance(cur, IndentedBlock):
+ prev.add_parse_action(
+ lambda s, l, t, cur_=cur: setattr(
+ cur_, "parent_anchor", col(l, s)
+ )
+ )
+ break
+ subs = cur.recurse()
+ cur = next(iter(subs), None)
+
+ self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
+ return self
+
+ def parseImpl(self, instring, loc, doActions=True):
+ # pass False as callPreParse arg to _parse for first element, since we already
+ # pre-parsed the string as part of our And pre-parsing
+ loc, resultlist = self.exprs[0]._parse(
+ instring, loc, doActions, callPreParse=False
+ )
+ errorStop = False
+ for e in self.exprs[1:]:
+ # if isinstance(e, And._ErrorStop):
+ if type(e) is And._ErrorStop:
+ errorStop = True
+ continue
+ if errorStop:
+ try:
+ loc, exprtokens = e._parse(instring, loc, doActions)
+ except ParseSyntaxException:
+ raise
+ except ParseBaseException as pe:
+ pe.__traceback__ = None
+ raise ParseSyntaxException._from_exception(pe)
+ except IndexError:
+ raise ParseSyntaxException(
+ instring, len(instring), self.errmsg, self
+ )
+ else:
+ loc, exprtokens = e._parse(instring, loc, doActions)
+ if exprtokens or exprtokens.haskeys():
+ resultlist += exprtokens
+ return loc, resultlist
+
+ def __iadd__(self, other):
+ if isinstance(other, str_type):
+ other = self._literalStringClass(other)
+ return self.append(other) # And([self, other])
+
+ def _checkRecursion(self, parseElementList):
+ subRecCheckList = parseElementList[:] + [self]
+ for e in self.exprs:
+ e._checkRecursion(subRecCheckList)
+ if not e.mayReturnEmpty:
+ break
+
+ def _generateDefaultName(self):
+ inner = " ".join(str(e) for e in self.exprs)
+ # strip off redundant inner {}'s
+ while len(inner) > 1 and inner[0 :: len(inner) - 1] == "{}":
+ inner = inner[1:-1]
+ return "{" + inner + "}"
+
+
+class Or(ParseExpression):
+ """Requires that at least one :class:`ParseExpression` is found. If
+ two expressions match, the expression that matches the longest
+ string will be used. May be constructed using the ``'^'``
+ operator.
+
+ Example::
+
+ # construct Or using '^' operator
+
+ number = Word(nums) ^ Combine(Word(nums) + '.' + Word(nums))
+ print(number.search_string("123 3.1416 789"))
+
+ prints::
+
+ [['123'], ['3.1416'], ['789']]
+ """
+
+ def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
+ super().__init__(exprs, savelist)
+ if self.exprs:
+ self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
+ self.skipWhitespace = all(e.skipWhitespace for e in self.exprs)
+ else:
+ self.mayReturnEmpty = True
+
+ def streamline(self) -> ParserElement:
+ super().streamline()
+ if self.exprs:
+ self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
+ self.saveAsList = any(e.saveAsList for e in self.exprs)
+ self.skipWhitespace = all(
+ e.skipWhitespace and not isinstance(e, White) for e in self.exprs
+ )
+ else:
+ self.saveAsList = False
+ return self
+
+ def parseImpl(self, instring, loc, doActions=True):
+ maxExcLoc = -1
+ maxException = None
+ matches = []
+ fatals = []
+ if all(e.callPreparse for e in self.exprs):
+ loc = self.preParse(instring, loc)
+ for e in self.exprs:
+ try:
+ loc2 = e.try_parse(instring, loc, raise_fatal=True)
+ except ParseFatalException as pfe:
+ pfe.__traceback__ = None
+ pfe.parserElement = e
+ fatals.append(pfe)
+ maxException = None
+ maxExcLoc = -1
+ except ParseException as err:
+ if not fatals:
+ err.__traceback__ = None
+ if err.loc > maxExcLoc:
+ maxException = err
+ maxExcLoc = err.loc
+ except IndexError:
+ if len(instring) > maxExcLoc:
+ maxException = ParseException(
+ instring, len(instring), e.errmsg, self
+ )
+ maxExcLoc = len(instring)
+ else:
+ # save match among all matches, to retry longest to shortest
+ matches.append((loc2, e))
+
+ if matches:
+ # re-evaluate all matches in descending order of length of match, in case attached actions
+ # might change whether or how much they match of the input.
+ matches.sort(key=itemgetter(0), reverse=True)
+
+ if not doActions:
+ # no further conditions or parse actions to change the selection of
+ # alternative, so the first match will be the best match
+ best_expr = matches[0][1]
+ return best_expr._parse(instring, loc, doActions)
+
+ longest = -1, None
+ for loc1, expr1 in matches:
+ if loc1 <= longest[0]:
+ # already have a longer match than this one will deliver, we are done
+ return longest
+
+ try:
+ loc2, toks = expr1._parse(instring, loc, doActions)
+ except ParseException as err:
+ err.__traceback__ = None
+ if err.loc > maxExcLoc:
+ maxException = err
+ maxExcLoc = err.loc
+ else:
+ if loc2 >= loc1:
+ return loc2, toks
+ # didn't match as much as before
+ elif loc2 > longest[0]:
+ longest = loc2, toks
+
+ if longest != (-1, None):
+ return longest
+
+ if fatals:
+ if len(fatals) > 1:
+ fatals.sort(key=lambda e: -e.loc)
+ if fatals[0].loc == fatals[1].loc:
+ fatals.sort(key=lambda e: (-e.loc, -len(str(e.parserElement))))
+ max_fatal = fatals[0]
+ raise max_fatal
+
+ if maxException is not None:
+ maxException.msg = self.errmsg
+ raise maxException
+ else:
+ raise ParseException(
+ instring, loc, "no defined alternatives to match", self
+ )
+
+ def __ixor__(self, other):
+ if isinstance(other, str_type):
+ other = self._literalStringClass(other)
+ return self.append(other) # Or([self, other])
+
+ def _generateDefaultName(self):
+ return "{" + " ^ ".join(str(e) for e in self.exprs) + "}"
+
+ def _setResultsName(self, name, listAllMatches=False):
+ if (
+ __diag__.warn_multiple_tokens_in_named_alternation
+ and Diagnostics.warn_multiple_tokens_in_named_alternation
+ not in self.suppress_warnings_
+ ):
+ if any(
+ isinstance(e, And)
+ and Diagnostics.warn_multiple_tokens_in_named_alternation
+ not in e.suppress_warnings_
+ for e in self.exprs
+ ):
+ warnings.warn(
+ "{}: setting results name {!r} on {} expression "
+ "will return a list of all parsed tokens in an And alternative, "
+ "in prior versions only the first token was returned; enclose "
+ "contained argument in Group".format(
+ "warn_multiple_tokens_in_named_alternation",
+ name,
+ type(self).__name__,
+ ),
+ stacklevel=3,
+ )
+
+ return super()._setResultsName(name, listAllMatches)
+
+
+class MatchFirst(ParseExpression):
+ """Requires that at least one :class:`ParseExpression` is found. If
+ more than one expression matches, the first one listed is the one that will
+ match. May be constructed using the ``'|'`` operator.
+
+ Example::
+
+ # construct MatchFirst using '|' operator
+
+ # watch the order of expressions to match
+ number = Word(nums) | Combine(Word(nums) + '.' + Word(nums))
+ print(number.search_string("123 3.1416 789")) # Fail! -> [['123'], ['3'], ['1416'], ['789']]
+
+ # put more selective expression first
+ number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums)
+ print(number.search_string("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']]
+ """
+
+ def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
+ super().__init__(exprs, savelist)
+ if self.exprs:
+ self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
+ self.skipWhitespace = all(e.skipWhitespace for e in self.exprs)
+ else:
+ self.mayReturnEmpty = True
+
+ def streamline(self) -> ParserElement:
+ if self.streamlined:
+ return self
+
+ super().streamline()
+ if self.exprs:
+ self.saveAsList = any(e.saveAsList for e in self.exprs)
+ self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
+ self.skipWhitespace = all(
+ e.skipWhitespace and not isinstance(e, White) for e in self.exprs
+ )
+ else:
+ self.saveAsList = False
+ self.mayReturnEmpty = True
+ return self
+
+ def parseImpl(self, instring, loc, doActions=True):
+ maxExcLoc = -1
+ maxException = None
+
+ for e in self.exprs:
+ try:
+ return e._parse(
+ instring,
+ loc,
+ doActions,
+ )
+ except ParseFatalException as pfe:
+ pfe.__traceback__ = None
+ pfe.parserElement = e
+ raise
+ except ParseException as err:
+ if err.loc > maxExcLoc:
+ maxException = err
+ maxExcLoc = err.loc
+ except IndexError:
+ if len(instring) > maxExcLoc:
+ maxException = ParseException(
+ instring, len(instring), e.errmsg, self
+ )
+ maxExcLoc = len(instring)
+
+ if maxException is not None:
+ maxException.msg = self.errmsg
+ raise maxException
+ else:
+ raise ParseException(
+ instring, loc, "no defined alternatives to match", self
+ )
+
+ def __ior__(self, other):
+ if isinstance(other, str_type):
+ other = self._literalStringClass(other)
+ return self.append(other) # MatchFirst([self, other])
+
+ def _generateDefaultName(self):
+ return "{" + " | ".join(str(e) for e in self.exprs) + "}"
+
+ def _setResultsName(self, name, listAllMatches=False):
+ if (
+ __diag__.warn_multiple_tokens_in_named_alternation
+ and Diagnostics.warn_multiple_tokens_in_named_alternation
+ not in self.suppress_warnings_
+ ):
+ if any(
+ isinstance(e, And)
+ and Diagnostics.warn_multiple_tokens_in_named_alternation
+ not in e.suppress_warnings_
+ for e in self.exprs
+ ):
+ warnings.warn(
+ "{}: setting results name {!r} on {} expression "
+ "will return a list of all parsed tokens in an And alternative, "
+ "in prior versions only the first token was returned; enclose "
+ "contained argument in Group".format(
+ "warn_multiple_tokens_in_named_alternation",
+ name,
+ type(self).__name__,
+ ),
+ stacklevel=3,
+ )
+
+ return super()._setResultsName(name, listAllMatches)
+
+
+class Each(ParseExpression):
+ """Requires all given :class:`ParseExpression` s to be found, but in
+ any order. Expressions may be separated by whitespace.
+
+ May be constructed using the ``'&'`` operator.
+
+ Example::
+
+ color = one_of("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN")
+ shape_type = one_of("SQUARE CIRCLE TRIANGLE STAR HEXAGON OCTAGON")
+ integer = Word(nums)
+ shape_attr = "shape:" + shape_type("shape")
+ posn_attr = "posn:" + Group(integer("x") + ',' + integer("y"))("posn")
+ color_attr = "color:" + color("color")
+ size_attr = "size:" + integer("size")
+
+ # use Each (using operator '&') to accept attributes in any order
+ # (shape and posn are required, color and size are optional)
+ shape_spec = shape_attr & posn_attr & Opt(color_attr) & Opt(size_attr)
+
+ shape_spec.run_tests('''
+ shape: SQUARE color: BLACK posn: 100, 120
+ shape: CIRCLE size: 50 color: BLUE posn: 50,80
+ color:GREEN size:20 shape:TRIANGLE posn:20,40
+ '''
+ )
+
+ prints::
+
+ shape: SQUARE color: BLACK posn: 100, 120
+ ['shape:', 'SQUARE', 'color:', 'BLACK', 'posn:', ['100', ',', '120']]
+ - color: BLACK
+ - posn: ['100', ',', '120']
+ - x: 100
+ - y: 120
+ - shape: SQUARE
+
+
+ shape: CIRCLE size: 50 color: BLUE posn: 50,80
+ ['shape:', 'CIRCLE', 'size:', '50', 'color:', 'BLUE', 'posn:', ['50', ',', '80']]
+ - color: BLUE
+ - posn: ['50', ',', '80']
+ - x: 50
+ - y: 80
+ - shape: CIRCLE
+ - size: 50
+
+
+ color: GREEN size: 20 shape: TRIANGLE posn: 20,40
+ ['color:', 'GREEN', 'size:', '20', 'shape:', 'TRIANGLE', 'posn:', ['20', ',', '40']]
+ - color: GREEN
+ - posn: ['20', ',', '40']
+ - x: 20
+ - y: 40
+ - shape: TRIANGLE
+ - size: 20
+ """
+
+ def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = True):
+ super().__init__(exprs, savelist)
+ if self.exprs:
+ self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
+ else:
+ self.mayReturnEmpty = True
+ self.skipWhitespace = True
+ self.initExprGroups = True
+ self.saveAsList = True
+
+ def streamline(self) -> ParserElement:
+ super().streamline()
+ if self.exprs:
+ self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
+ else:
+ self.mayReturnEmpty = True
+ return self
+
+ def parseImpl(self, instring, loc, doActions=True):
+ if self.initExprGroups:
+ self.opt1map = dict(
+ (id(e.expr), e) for e in self.exprs if isinstance(e, Opt)
+ )
+ opt1 = [e.expr for e in self.exprs if isinstance(e, Opt)]
+ opt2 = [
+ e
+ for e in self.exprs
+ if e.mayReturnEmpty and not isinstance(e, (Opt, Regex, ZeroOrMore))
+ ]
+ self.optionals = opt1 + opt2
+ self.multioptionals = [
+ e.expr.set_results_name(e.resultsName, list_all_matches=True)
+ for e in self.exprs
+ if isinstance(e, _MultipleMatch)
+ ]
+ self.multirequired = [
+ e.expr.set_results_name(e.resultsName, list_all_matches=True)
+ for e in self.exprs
+ if isinstance(e, OneOrMore)
+ ]
+ self.required = [
+ e for e in self.exprs if not isinstance(e, (Opt, ZeroOrMore, OneOrMore))
+ ]
+ self.required += self.multirequired
+ self.initExprGroups = False
+
+ tmpLoc = loc
+ tmpReqd = self.required[:]
+ tmpOpt = self.optionals[:]
+ multis = self.multioptionals[:]
+ matchOrder = []
+
+ keepMatching = True
+ failed = []
+ fatals = []
+ while keepMatching:
+ tmpExprs = tmpReqd + tmpOpt + multis
+ failed.clear()
+ fatals.clear()
+ for e in tmpExprs:
+ try:
+ tmpLoc = e.try_parse(instring, tmpLoc, raise_fatal=True)
+ except ParseFatalException as pfe:
+ pfe.__traceback__ = None
+ pfe.parserElement = e
+ fatals.append(pfe)
+ failed.append(e)
+ except ParseException:
+ failed.append(e)
+ else:
+ matchOrder.append(self.opt1map.get(id(e), e))
+ if e in tmpReqd:
+ tmpReqd.remove(e)
+ elif e in tmpOpt:
+ tmpOpt.remove(e)
+ if len(failed) == len(tmpExprs):
+ keepMatching = False
+
+ # look for any ParseFatalExceptions
+ if fatals:
+ if len(fatals) > 1:
+ fatals.sort(key=lambda e: -e.loc)
+ if fatals[0].loc == fatals[1].loc:
+ fatals.sort(key=lambda e: (-e.loc, -len(str(e.parserElement))))
+ max_fatal = fatals[0]
+ raise max_fatal
+
+ if tmpReqd:
+ missing = ", ".join([str(e) for e in tmpReqd])
+ raise ParseException(
+ instring,
+ loc,
+ "Missing one or more required elements ({})".format(missing),
+ )
+
+ # add any unmatched Opts, in case they have default values defined
+ matchOrder += [e for e in self.exprs if isinstance(e, Opt) and e.expr in tmpOpt]
+
+ total_results = ParseResults([])
+ for e in matchOrder:
+ loc, results = e._parse(instring, loc, doActions)
+ total_results += results
+
+ return loc, total_results
+
+ def _generateDefaultName(self):
+ return "{" + " & ".join(str(e) for e in self.exprs) + "}"
+
+
+class ParseElementEnhance(ParserElement):
+ """Abstract subclass of :class:`ParserElement`, for combining and
+ post-processing parsed tokens.
+ """
+
+ def __init__(self, expr: Union[ParserElement, str], savelist: bool = False):
+ super().__init__(savelist)
+ if isinstance(expr, str_type):
+ if issubclass(self._literalStringClass, Token):
+ expr = self._literalStringClass(expr)
+ elif issubclass(type(self), self._literalStringClass):
+ expr = Literal(expr)
+ else:
+ expr = self._literalStringClass(Literal(expr))
+ self.expr = expr
+ if expr is not None:
+ self.mayIndexError = expr.mayIndexError
+ self.mayReturnEmpty = expr.mayReturnEmpty
+ self.set_whitespace_chars(
+ expr.whiteChars, copy_defaults=expr.copyDefaultWhiteChars
+ )
+ self.skipWhitespace = expr.skipWhitespace
+ self.saveAsList = expr.saveAsList
+ self.callPreparse = expr.callPreparse
+ self.ignoreExprs.extend(expr.ignoreExprs)
+
+ def recurse(self) -> Sequence[ParserElement]:
+ return [self.expr] if self.expr is not None else []
+
+ def parseImpl(self, instring, loc, doActions=True):
+ if self.expr is not None:
+ return self.expr._parse(instring, loc, doActions, callPreParse=False)
+ else:
+ raise ParseException(instring, loc, "No expression defined", self)
+
+ def leave_whitespace(self, recursive: bool = True) -> ParserElement:
+ super().leave_whitespace(recursive)
+
+ if recursive:
+ self.expr = self.expr.copy()
+ if self.expr is not None:
+ self.expr.leave_whitespace(recursive)
+ return self
+
+ def ignore_whitespace(self, recursive: bool = True) -> ParserElement:
+ super().ignore_whitespace(recursive)
+
+ if recursive:
+ self.expr = self.expr.copy()
+ if self.expr is not None:
+ self.expr.ignore_whitespace(recursive)
+ return self
+
+ def ignore(self, other) -> ParserElement:
+ if isinstance(other, Suppress):
+ if other not in self.ignoreExprs:
+ super().ignore(other)
+ if self.expr is not None:
+ self.expr.ignore(self.ignoreExprs[-1])
+ else:
+ super().ignore(other)
+ if self.expr is not None:
+ self.expr.ignore(self.ignoreExprs[-1])
+ return self
+
+ def streamline(self) -> ParserElement:
+ super().streamline()
+ if self.expr is not None:
+ self.expr.streamline()
+ return self
+
+ def _checkRecursion(self, parseElementList):
+ if self in parseElementList:
+ raise RecursiveGrammarException(parseElementList + [self])
+ subRecCheckList = parseElementList[:] + [self]
+ if self.expr is not None:
+ self.expr._checkRecursion(subRecCheckList)
+
+ def validate(self, validateTrace=None) -> None:
+ if validateTrace is None:
+ validateTrace = []
+ tmp = validateTrace[:] + [self]
+ if self.expr is not None:
+ self.expr.validate(tmp)
+ self._checkRecursion([])
+
+ def _generateDefaultName(self):
+ return "{}:({})".format(self.__class__.__name__, str(self.expr))
+
+ ignoreWhitespace = ignore_whitespace
+ leaveWhitespace = leave_whitespace
+
+
+class IndentedBlock(ParseElementEnhance):
+ """
+ Expression to match one or more expressions at a given indentation level.
+ Useful for parsing text where structure is implied by indentation (like Python source code).
+ """
+
+ class _Indent(Empty):
+ def __init__(self, ref_col: int):
+ super().__init__()
+ self.errmsg = "expected indent at column {}".format(ref_col)
+ self.add_condition(lambda s, l, t: col(l, s) == ref_col)
+
+ class _IndentGreater(Empty):
+ def __init__(self, ref_col: int):
+ super().__init__()
+ self.errmsg = "expected indent at column greater than {}".format(ref_col)
+ self.add_condition(lambda s, l, t: col(l, s) > ref_col)
+
+ def __init__(
+ self, expr: ParserElement, *, recursive: bool = False, grouped: bool = True
+ ):
+ super().__init__(expr, savelist=True)
+ # if recursive:
+ # raise NotImplementedError("IndentedBlock with recursive is not implemented")
+ self._recursive = recursive
+ self._grouped = grouped
+ self.parent_anchor = 1
+
+ def parseImpl(self, instring, loc, doActions=True):
+ # advance parse position to non-whitespace by using an Empty()
+ # this should be the column to be used for all subsequent indented lines
+ anchor_loc = Empty().preParse(instring, loc)
+
+ # see if self.expr matches at the current location - if not it will raise an exception
+ # and no further work is necessary
+ self.expr.try_parse(instring, anchor_loc, doActions)
+
+ indent_col = col(anchor_loc, instring)
+ peer_detect_expr = self._Indent(indent_col)
+
+ inner_expr = Empty() + peer_detect_expr + self.expr
+ if self._recursive:
+ sub_indent = self._IndentGreater(indent_col)
+ nested_block = IndentedBlock(
+ self.expr, recursive=self._recursive, grouped=self._grouped
+ )
+ nested_block.set_debug(self.debug)
+ nested_block.parent_anchor = indent_col
+ inner_expr += Opt(sub_indent + nested_block)
+
+ inner_expr.set_name(f"inner {hex(id(inner_expr))[-4:].upper()}@{indent_col}")
+ block = OneOrMore(inner_expr)
+
+ trailing_undent = self._Indent(self.parent_anchor) | StringEnd()
+
+ if self._grouped:
+ wrapper = Group
+ else:
+ wrapper = lambda expr: expr
+ return (wrapper(block) + Optional(trailing_undent)).parseImpl(
+ instring, anchor_loc, doActions
+ )
+
+
+class AtStringStart(ParseElementEnhance):
+ """Matches if expression matches at the beginning of the parse
+ string::
+
+ AtStringStart(Word(nums)).parse_string("123")
+ # prints ["123"]
+
+ AtStringStart(Word(nums)).parse_string(" 123")
+ # raises ParseException
+ """
+
+ def __init__(self, expr: Union[ParserElement, str]):
+ super().__init__(expr)
+ self.callPreparse = False
+
+ def parseImpl(self, instring, loc, doActions=True):
+ if loc != 0:
+ raise ParseException(instring, loc, "not found at string start")
+ return super().parseImpl(instring, loc, doActions)
+
+
+class AtLineStart(ParseElementEnhance):
+ r"""Matches if an expression matches at the beginning of a line within
+ the parse string
+
+ Example::
+
+ test = '''\
+ AAA this line
+ AAA and this line
+ AAA but not this one
+ B AAA and definitely not this one
+ '''
+
+ for t in (AtLineStart('AAA') + restOfLine).search_string(test):
+ print(t)
+
+ prints::
+
+ ['AAA', ' this line']
+ ['AAA', ' and this line']
+
+ """
+
+ def __init__(self, expr: Union[ParserElement, str]):
+ super().__init__(expr)
+ self.callPreparse = False
+
+ def parseImpl(self, instring, loc, doActions=True):
+ if col(loc, instring) != 1:
+ raise ParseException(instring, loc, "not found at line start")
+ return super().parseImpl(instring, loc, doActions)
+
+
+class FollowedBy(ParseElementEnhance):
+ """Lookahead matching of the given parse expression.
+ ``FollowedBy`` does *not* advance the parsing position within
+ the input string, it only verifies that the specified parse
+ expression matches at the current position. ``FollowedBy``
+ always returns a null token list. If any results names are defined
+ in the lookahead expression, those *will* be returned for access by
+ name.
+
+ Example::
+
+ # use FollowedBy to match a label only if it is followed by a ':'
+ data_word = Word(alphas)
+ label = data_word + FollowedBy(':')
+ attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
+
+ attr_expr[1, ...].parse_string("shape: SQUARE color: BLACK posn: upper left").pprint()
+
+ prints::
+
+ [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']]
+ """
+
+ def __init__(self, expr: Union[ParserElement, str]):
+ super().__init__(expr)
+ self.mayReturnEmpty = True
+
+ def parseImpl(self, instring, loc, doActions=True):
+ # by using self._expr.parse and deleting the contents of the returned ParseResults list
+ # we keep any named results that were defined in the FollowedBy expression
+ _, ret = self.expr._parse(instring, loc, doActions=doActions)
+ del ret[:]
+
+ return loc, ret
+
+
+class PrecededBy(ParseElementEnhance):
+ """Lookbehind matching of the given parse expression.
+ ``PrecededBy`` does not advance the parsing position within the
+ input string, it only verifies that the specified parse expression
+ matches prior to the current position. ``PrecededBy`` always
+ returns a null token list, but if a results name is defined on the
+ given expression, it is returned.
+
+ Parameters:
+
+ - expr - expression that must match prior to the current parse
+ location
+ - retreat - (default= ``None``) - (int) maximum number of characters
+ to lookbehind prior to the current parse location
+
+ If the lookbehind expression is a string, :class:`Literal`,
+ :class:`Keyword`, or a :class:`Word` or :class:`CharsNotIn`
+ with a specified exact or maximum length, then the retreat
+ parameter is not required. Otherwise, retreat must be specified to
+ give a maximum number of characters to look back from
+ the current parse position for a lookbehind match.
+
+ Example::
+
+ # VB-style variable names with type prefixes
+ int_var = PrecededBy("#") + pyparsing_common.identifier
+ str_var = PrecededBy("$") + pyparsing_common.identifier
+
+ """
+
+ def __init__(
+ self, expr: Union[ParserElement, str], retreat: typing.Optional[int] = None
+ ):
+ super().__init__(expr)
+ self.expr = self.expr().leave_whitespace()
+ self.mayReturnEmpty = True
+ self.mayIndexError = False
+ self.exact = False
+ if isinstance(expr, str_type):
+ retreat = len(expr)
+ self.exact = True
+ elif isinstance(expr, (Literal, Keyword)):
+ retreat = expr.matchLen
+ self.exact = True
+ elif isinstance(expr, (Word, CharsNotIn)) and expr.maxLen != _MAX_INT:
+ retreat = expr.maxLen
+ self.exact = True
+ elif isinstance(expr, PositionToken):
+ retreat = 0
+ self.exact = True
+ self.retreat = retreat
+ self.errmsg = "not preceded by " + str(expr)
+ self.skipWhitespace = False
+ self.parseAction.append(lambda s, l, t: t.__delitem__(slice(None, None)))
+
+ def parseImpl(self, instring, loc=0, doActions=True):
+ if self.exact:
+ if loc < self.retreat:
+ raise ParseException(instring, loc, self.errmsg)
+ start = loc - self.retreat
+ _, ret = self.expr._parse(instring, start)
+ else:
+ # retreat specified a maximum lookbehind window, iterate
+ test_expr = self.expr + StringEnd()
+ instring_slice = instring[max(0, loc - self.retreat) : loc]
+ last_expr = ParseException(instring, loc, self.errmsg)
+ for offset in range(1, min(loc, self.retreat + 1) + 1):
+ try:
+ # print('trying', offset, instring_slice, repr(instring_slice[loc - offset:]))
+ _, ret = test_expr._parse(
+ instring_slice, len(instring_slice) - offset
+ )
+ except ParseBaseException as pbe:
+ last_expr = pbe
+ else:
+ break
+ else:
+ raise last_expr
+ return loc, ret
+
+
+class Located(ParseElementEnhance):
+ """
+ Decorates a returned token with its starting and ending
+ locations in the input string.
+
+ This helper adds the following results names:
+
+ - ``locn_start`` - location where matched expression begins
+ - ``locn_end`` - location where matched expression ends
+ - ``value`` - the actual parsed results
+
+ Be careful if the input text contains ``<TAB>`` characters, you
+ may want to call :class:`ParserElement.parse_with_tabs`
+
+ Example::
+
+ wd = Word(alphas)
+ for match in Located(wd).search_string("ljsdf123lksdjjf123lkkjj1222"):
+ print(match)
+
+ prints::
+
+ [0, ['ljsdf'], 5]
+ [8, ['lksdjjf'], 15]
+ [18, ['lkkjj'], 23]
+
+ """
+
+ def parseImpl(self, instring, loc, doActions=True):
+ start = loc
+ loc, tokens = self.expr._parse(instring, start, doActions, callPreParse=False)
+ ret_tokens = ParseResults([start, tokens, loc])
+ ret_tokens["locn_start"] = start
+ ret_tokens["value"] = tokens
+ ret_tokens["locn_end"] = loc
+ if self.resultsName:
+ # must return as a list, so that the name will be attached to the complete group
+ return loc, [ret_tokens]
+ else:
+ return loc, ret_tokens
+
+
+class NotAny(ParseElementEnhance):
+ """
+ Lookahead to disallow matching with the given parse expression.
+ ``NotAny`` does *not* advance the parsing position within the
+ input string, it only verifies that the specified parse expression
+ does *not* match at the current position. Also, ``NotAny`` does
+ *not* skip over leading whitespace. ``NotAny`` always returns
+ a null token list. May be constructed using the ``'~'`` operator.
+
+ Example::
+
+ AND, OR, NOT = map(CaselessKeyword, "AND OR NOT".split())
+
+ # take care not to mistake keywords for identifiers
+ ident = ~(AND | OR | NOT) + Word(alphas)
+ boolean_term = Opt(NOT) + ident
+
+ # very crude boolean expression - to support parenthesis groups and
+ # operation hierarchy, use infix_notation
+ boolean_expr = boolean_term + ((AND | OR) + boolean_term)[...]
+
+ # integers that are followed by "." are actually floats
+ integer = Word(nums) + ~Char(".")
+ """
+
+ def __init__(self, expr: Union[ParserElement, str]):
+ super().__init__(expr)
+ # do NOT use self.leave_whitespace(), don't want to propagate to exprs
+ # self.leave_whitespace()
+ self.skipWhitespace = False
+
+ self.mayReturnEmpty = True
+ self.errmsg = "Found unwanted token, " + str(self.expr)
+
+ def parseImpl(self, instring, loc, doActions=True):
+ if self.expr.can_parse_next(instring, loc):
+ raise ParseException(instring, loc, self.errmsg, self)
+ return loc, []
+
+ def _generateDefaultName(self):
+ return "~{" + str(self.expr) + "}"
+
+
+class _MultipleMatch(ParseElementEnhance):
+ def __init__(
+ self,
+ expr: ParserElement,
+ stop_on: typing.Optional[Union[ParserElement, str]] = None,
+ *,
+ stopOn: typing.Optional[Union[ParserElement, str]] = None,
+ ):
+ super().__init__(expr)
+ stopOn = stopOn or stop_on
+ self.saveAsList = True
+ ender = stopOn
+ if isinstance(ender, str_type):
+ ender = self._literalStringClass(ender)
+ self.stopOn(ender)
+
+ def stopOn(self, ender) -> ParserElement:
+ if isinstance(ender, str_type):
+ ender = self._literalStringClass(ender)
+ self.not_ender = ~ender if ender is not None else None
+ return self
+
+ def parseImpl(self, instring, loc, doActions=True):
+ self_expr_parse = self.expr._parse
+ self_skip_ignorables = self._skipIgnorables
+ check_ender = self.not_ender is not None
+ if check_ender:
+ try_not_ender = self.not_ender.tryParse
+
+ # must be at least one (but first see if we are the stopOn sentinel;
+ # if so, fail)
+ if check_ender:
+ try_not_ender(instring, loc)
+ loc, tokens = self_expr_parse(instring, loc, doActions)
+ try:
+ hasIgnoreExprs = not not self.ignoreExprs
+ while 1:
+ if check_ender:
+ try_not_ender(instring, loc)
+ if hasIgnoreExprs:
+ preloc = self_skip_ignorables(instring, loc)
+ else:
+ preloc = loc
+ loc, tmptokens = self_expr_parse(instring, preloc, doActions)
+ if tmptokens or tmptokens.haskeys():
+ tokens += tmptokens
+ except (ParseException, IndexError):
+ pass
+
+ return loc, tokens
+
+ def _setResultsName(self, name, listAllMatches=False):
+ if (
+ __diag__.warn_ungrouped_named_tokens_in_collection
+ and Diagnostics.warn_ungrouped_named_tokens_in_collection
+ not in self.suppress_warnings_
+ ):
+ for e in [self.expr] + self.expr.recurse():
+ if (
+ isinstance(e, ParserElement)
+ and e.resultsName
+ and Diagnostics.warn_ungrouped_named_tokens_in_collection
+ not in e.suppress_warnings_
+ ):
+ warnings.warn(
+ "{}: setting results name {!r} on {} expression "
+ "collides with {!r} on contained expression".format(
+ "warn_ungrouped_named_tokens_in_collection",
+ name,
+ type(self).__name__,
+ e.resultsName,
+ ),
+ stacklevel=3,
+ )
+
+ return super()._setResultsName(name, listAllMatches)
+
+
+class OneOrMore(_MultipleMatch):
+ """
+ Repetition of one or more of the given expression.
+
+ Parameters:
+ - expr - expression that must match one or more times
+ - stop_on - (default= ``None``) - expression for a terminating sentinel
+ (only required if the sentinel would ordinarily match the repetition
+ expression)
+
+ Example::
+
+ data_word = Word(alphas)
+ label = data_word + FollowedBy(':')
+ attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).set_parse_action(' '.join))
+
+ text = "shape: SQUARE posn: upper left color: BLACK"
+ attr_expr[1, ...].parse_string(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
+
+ # use stop_on attribute for OneOrMore to avoid reading label string as part of the data
+ attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
+ OneOrMore(attr_expr).parse_string(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']]
+
+ # could also be written as
+ (attr_expr * (1,)).parse_string(text).pprint()
+ """
+
+ def _generateDefaultName(self):
+ return "{" + str(self.expr) + "}..."
+
+
+class ZeroOrMore(_MultipleMatch):
+ """
+ Optional repetition of zero or more of the given expression.
+
+ Parameters:
+ - ``expr`` - expression that must match zero or more times
+ - ``stop_on`` - expression for a terminating sentinel
+ (only required if the sentinel would ordinarily match the repetition
+ expression) - (default= ``None``)
+
+ Example: similar to :class:`OneOrMore`
+ """
+
+ def __init__(
+ self,
+ expr: ParserElement,
+ stop_on: typing.Optional[Union[ParserElement, str]] = None,
+ *,
+ stopOn: typing.Optional[Union[ParserElement, str]] = None,
+ ):
+ super().__init__(expr, stopOn=stopOn or stop_on)
+ self.mayReturnEmpty = True
+
+ def parseImpl(self, instring, loc, doActions=True):
+ try:
+ return super().parseImpl(instring, loc, doActions)
+ except (ParseException, IndexError):
+ return loc, ParseResults([], name=self.resultsName)
+
+ def _generateDefaultName(self):
+ return "[" + str(self.expr) + "]..."
+
+
+class _NullToken:
+ def __bool__(self):
+ return False
+
+ def __str__(self):
+ return ""
+
+
+class Opt(ParseElementEnhance):
+ """
+ Optional matching of the given expression.
+
+ Parameters:
+ - ``expr`` - expression that must match zero or more times
+ - ``default`` (optional) - value to be returned if the optional expression is not found.
+
+ Example::
+
+ # US postal code can be a 5-digit zip, plus optional 4-digit qualifier
+ zip = Combine(Word(nums, exact=5) + Opt('-' + Word(nums, exact=4)))
+ zip.run_tests('''
+ # traditional ZIP code
+ 12345
+
+ # ZIP+4 form
+ 12101-0001
+
+ # invalid ZIP
+ 98765-
+ ''')
+
+ prints::
+
+ # traditional ZIP code
+ 12345
+ ['12345']
+
+ # ZIP+4 form
+ 12101-0001
+ ['12101-0001']
+
+ # invalid ZIP
+ 98765-
+ ^
+ FAIL: Expected end of text (at char 5), (line:1, col:6)
+ """
+
+ __optionalNotMatched = _NullToken()
+
+ def __init__(
+ self, expr: Union[ParserElement, str], default: Any = __optionalNotMatched
+ ):
+ super().__init__(expr, savelist=False)
+ self.saveAsList = self.expr.saveAsList
+ self.defaultValue = default
+ self.mayReturnEmpty = True
+
+ def parseImpl(self, instring, loc, doActions=True):
+ self_expr = self.expr
+ try:
+ loc, tokens = self_expr._parse(instring, loc, doActions, callPreParse=False)
+ except (ParseException, IndexError):
+ default_value = self.defaultValue
+ if default_value is not self.__optionalNotMatched:
+ if self_expr.resultsName:
+ tokens = ParseResults([default_value])
+ tokens[self_expr.resultsName] = default_value
+ else:
+ tokens = [default_value]
+ else:
+ tokens = []
+ return loc, tokens
+
+ def _generateDefaultName(self):
+ inner = str(self.expr)
+ # strip off redundant inner {}'s
+ while len(inner) > 1 and inner[0 :: len(inner) - 1] == "{}":
+ inner = inner[1:-1]
+ return "[" + inner + "]"
+
+
+Optional = Opt
+
+
+class SkipTo(ParseElementEnhance):
+ """
+ Token for skipping over all undefined text until the matched
+ expression is found.
+
+ Parameters:
+ - ``expr`` - target expression marking the end of the data to be skipped
+ - ``include`` - if ``True``, the target expression is also parsed
+ (the skipped text and target expression are returned as a 2-element
+ list) (default= ``False``).
+ - ``ignore`` - (default= ``None``) used to define grammars (typically quoted strings and
+ comments) that might contain false matches to the target expression
+ - ``fail_on`` - (default= ``None``) define expressions that are not allowed to be
+ included in the skipped test; if found before the target expression is found,
+ the :class:`SkipTo` is not a match
+
+ Example::
+
+ report = '''
+ Outstanding Issues Report - 1 Jan 2000
+
+ # | Severity | Description | Days Open
+ -----+----------+-------------------------------------------+-----------
+ 101 | Critical | Intermittent system crash | 6
+ 94 | Cosmetic | Spelling error on Login ('log|n') | 14
+ 79 | Minor | System slow when running too many reports | 47
+ '''
+ integer = Word(nums)
+ SEP = Suppress('|')
+ # use SkipTo to simply match everything up until the next SEP
+ # - ignore quoted strings, so that a '|' character inside a quoted string does not match
+ # - parse action will call token.strip() for each matched token, i.e., the description body
+ string_data = SkipTo(SEP, ignore=quoted_string)
+ string_data.set_parse_action(token_map(str.strip))
+ ticket_expr = (integer("issue_num") + SEP
+ + string_data("sev") + SEP
+ + string_data("desc") + SEP
+ + integer("days_open"))
+
+ for tkt in ticket_expr.search_string(report):
+ print tkt.dump()
+
+ prints::
+
+ ['101', 'Critical', 'Intermittent system crash', '6']
+ - days_open: '6'
+ - desc: 'Intermittent system crash'
+ - issue_num: '101'
+ - sev: 'Critical'
+ ['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14']
+ - days_open: '14'
+ - desc: "Spelling error on Login ('log|n')"
+ - issue_num: '94'
+ - sev: 'Cosmetic'
+ ['79', 'Minor', 'System slow when running too many reports', '47']
+ - days_open: '47'
+ - desc: 'System slow when running too many reports'
+ - issue_num: '79'
+ - sev: 'Minor'
+ """
+
+ def __init__(
+ self,
+ other: Union[ParserElement, str],
+ include: bool = False,
+ ignore: bool = None,
+ fail_on: typing.Optional[Union[ParserElement, str]] = None,
+ *,
+ failOn: Union[ParserElement, str] = None,
+ ):
+ super().__init__(other)
+ failOn = failOn or fail_on
+ self.ignoreExpr = ignore
+ self.mayReturnEmpty = True
+ self.mayIndexError = False
+ self.includeMatch = include
+ self.saveAsList = False
+ if isinstance(failOn, str_type):
+ self.failOn = self._literalStringClass(failOn)
+ else:
+ self.failOn = failOn
+ self.errmsg = "No match found for " + str(self.expr)
+
+ def parseImpl(self, instring, loc, doActions=True):
+ startloc = loc
+ instrlen = len(instring)
+ self_expr_parse = self.expr._parse
+ self_failOn_canParseNext = (
+ self.failOn.canParseNext if self.failOn is not None else None
+ )
+ self_ignoreExpr_tryParse = (
+ self.ignoreExpr.tryParse if self.ignoreExpr is not None else None
+ )
+
+ tmploc = loc
+ while tmploc <= instrlen:
+ if self_failOn_canParseNext is not None:
+ # break if failOn expression matches
+ if self_failOn_canParseNext(instring, tmploc):
+ break
+
+ if self_ignoreExpr_tryParse is not None:
+ # advance past ignore expressions
+ while 1:
+ try:
+ tmploc = self_ignoreExpr_tryParse(instring, tmploc)
+ except ParseBaseException:
+ break
+
+ try:
+ self_expr_parse(instring, tmploc, doActions=False, callPreParse=False)
+ except (ParseException, IndexError):
+ # no match, advance loc in string
+ tmploc += 1
+ else:
+ # matched skipto expr, done
+ break
+
+ else:
+ # ran off the end of the input string without matching skipto expr, fail
+ raise ParseException(instring, loc, self.errmsg, self)
+
+ # build up return values
+ loc = tmploc
+ skiptext = instring[startloc:loc]
+ skipresult = ParseResults(skiptext)
+
+ if self.includeMatch:
+ loc, mat = self_expr_parse(instring, loc, doActions, callPreParse=False)
+ skipresult += mat
+
+ return loc, skipresult
+
+
+class Forward(ParseElementEnhance):
+ """
+ Forward declaration of an expression to be defined later -
+ used for recursive grammars, such as algebraic infix notation.
+ When the expression is known, it is assigned to the ``Forward``
+ variable using the ``'<<'`` operator.
+
+ Note: take care when assigning to ``Forward`` not to overlook
+ precedence of operators.
+
+ Specifically, ``'|'`` has a lower precedence than ``'<<'``, so that::
+
+ fwd_expr << a | b | c
+
+ will actually be evaluated as::
+
+ (fwd_expr << a) | b | c
+
+ thereby leaving b and c out as parseable alternatives. It is recommended that you
+ explicitly group the values inserted into the ``Forward``::
+
+ fwd_expr << (a | b | c)
+
+ Converting to use the ``'<<='`` operator instead will avoid this problem.
+
+ See :class:`ParseResults.pprint` for an example of a recursive
+ parser created using ``Forward``.
+ """
+
+ def __init__(self, other: typing.Optional[Union[ParserElement, str]] = None):
+ self.caller_frame = traceback.extract_stack(limit=2)[0]
+ super().__init__(other, savelist=False)
+ self.lshift_line = None
+
+ def __lshift__(self, other):
+ if hasattr(self, "caller_frame"):
+ del self.caller_frame
+ if isinstance(other, str_type):
+ other = self._literalStringClass(other)
+ self.expr = other
+ self.mayIndexError = self.expr.mayIndexError
+ self.mayReturnEmpty = self.expr.mayReturnEmpty
+ self.set_whitespace_chars(
+ self.expr.whiteChars, copy_defaults=self.expr.copyDefaultWhiteChars
+ )
+ self.skipWhitespace = self.expr.skipWhitespace
+ self.saveAsList = self.expr.saveAsList
+ self.ignoreExprs.extend(self.expr.ignoreExprs)
+ self.lshift_line = traceback.extract_stack(limit=2)[-2]
+ return self
+
+ def __ilshift__(self, other):
+ return self << other
+
+ def __or__(self, other):
+ caller_line = traceback.extract_stack(limit=2)[-2]
+ if (
+ __diag__.warn_on_match_first_with_lshift_operator
+ and caller_line == self.lshift_line
+ and Diagnostics.warn_on_match_first_with_lshift_operator
+ not in self.suppress_warnings_
+ ):
+ warnings.warn(
+ "using '<<' operator with '|' is probably an error, use '<<='",
+ stacklevel=2,
+ )
+ ret = super().__or__(other)
+ return ret
+
+ def __del__(self):
+ # see if we are getting dropped because of '=' reassignment of var instead of '<<=' or '<<'
+ if (
+ self.expr is None
+ and __diag__.warn_on_assignment_to_Forward
+ and Diagnostics.warn_on_assignment_to_Forward not in self.suppress_warnings_
+ ):
+ warnings.warn_explicit(
+ "Forward defined here but no expression attached later using '<<=' or '<<'",
+ UserWarning,
+ filename=self.caller_frame.filename,
+ lineno=self.caller_frame.lineno,
+ )
+
+ def parseImpl(self, instring, loc, doActions=True):
+ if (
+ self.expr is None
+ and __diag__.warn_on_parse_using_empty_Forward
+ and Diagnostics.warn_on_parse_using_empty_Forward
+ not in self.suppress_warnings_
+ ):
+ # walk stack until parse_string, scan_string, search_string, or transform_string is found
+ parse_fns = [
+ "parse_string",
+ "scan_string",
+ "search_string",
+ "transform_string",
+ ]
+ tb = traceback.extract_stack(limit=200)
+ for i, frm in enumerate(reversed(tb), start=1):
+ if frm.name in parse_fns:
+ stacklevel = i + 1
+ break
+ else:
+ stacklevel = 2
+ warnings.warn(
+ "Forward expression was never assigned a value, will not parse any input",
+ stacklevel=stacklevel,
+ )
+ if not ParserElement._left_recursion_enabled:
+ return super().parseImpl(instring, loc, doActions)
+ # ## Bounded Recursion algorithm ##
+ # Recursion only needs to be processed at ``Forward`` elements, since they are
+ # the only ones that can actually refer to themselves. The general idea is
+ # to handle recursion stepwise: We start at no recursion, then recurse once,
+ # recurse twice, ..., until more recursion offers no benefit (we hit the bound).
+ #
+ # The "trick" here is that each ``Forward`` gets evaluated in two contexts
+ # - to *match* a specific recursion level, and
+ # - to *search* the bounded recursion level
+ # and the two run concurrently. The *search* must *match* each recursion level
+ # to find the best possible match. This is handled by a memo table, which
+ # provides the previous match to the next level match attempt.
+ #
+ # See also "Left Recursion in Parsing Expression Grammars", Medeiros et al.
+ #
+ # There is a complication since we not only *parse* but also *transform* via
+ # actions: We do not want to run the actions too often while expanding. Thus,
+ # we expand using `doActions=False` and only run `doActions=True` if the next
+ # recursion level is acceptable.
+ with ParserElement.recursion_lock:
+ memo = ParserElement.recursion_memos
+ try:
+ # we are parsing at a specific recursion expansion - use it as-is
+ prev_loc, prev_result = memo[loc, self, doActions]
+ if isinstance(prev_result, Exception):
+ raise prev_result
+ return prev_loc, prev_result.copy()
+ except KeyError:
+ act_key = (loc, self, True)
+ peek_key = (loc, self, False)
+ # we are searching for the best recursion expansion - keep on improving
+ # both `doActions` cases must be tracked separately here!
+ prev_loc, prev_peek = memo[peek_key] = (
+ loc - 1,
+ ParseException(
+ instring, loc, "Forward recursion without base case", self
+ ),
+ )
+ if doActions:
+ memo[act_key] = memo[peek_key]
+ while True:
+ try:
+ new_loc, new_peek = super().parseImpl(instring, loc, False)
+ except ParseException:
+ # we failed before getting any match – do not hide the error
+ if isinstance(prev_peek, Exception):
+ raise
+ new_loc, new_peek = prev_loc, prev_peek
+ # the match did not get better: we are done
+ if new_loc <= prev_loc:
+ if doActions:
+ # replace the match for doActions=False as well,
+ # in case the action did backtrack
+ prev_loc, prev_result = memo[peek_key] = memo[act_key]
+ del memo[peek_key], memo[act_key]
+ return prev_loc, prev_result.copy()
+ del memo[peek_key]
+ return prev_loc, prev_peek.copy()
+ # the match did get better: see if we can improve further
+ else:
+ if doActions:
+ try:
+ memo[act_key] = super().parseImpl(instring, loc, True)
+ except ParseException as e:
+ memo[peek_key] = memo[act_key] = (new_loc, e)
+ raise
+ prev_loc, prev_peek = memo[peek_key] = new_loc, new_peek
+
+ def leave_whitespace(self, recursive: bool = True) -> ParserElement:
+ self.skipWhitespace = False
+ return self
+
+ def ignore_whitespace(self, recursive: bool = True) -> ParserElement:
+ self.skipWhitespace = True
+ return self
+
+ def streamline(self) -> ParserElement:
+ if not self.streamlined:
+ self.streamlined = True
+ if self.expr is not None:
+ self.expr.streamline()
+ return self
+
+ def validate(self, validateTrace=None) -> None:
+ if validateTrace is None:
+ validateTrace = []
+
+ if self not in validateTrace:
+ tmp = validateTrace[:] + [self]
+ if self.expr is not None:
+ self.expr.validate(tmp)
+ self._checkRecursion([])
+
+ def _generateDefaultName(self):
+ # Avoid infinite recursion by setting a temporary _defaultName
+ self._defaultName = ": ..."
+
+ # Use the string representation of main expression.
+ retString = "..."
+ try:
+ if self.expr is not None:
+ retString = str(self.expr)[:1000]
+ else:
+ retString = "None"
+ finally:
+ return self.__class__.__name__ + ": " + retString
+
+ def copy(self) -> ParserElement:
+ if self.expr is not None:
+ return super().copy()
+ else:
+ ret = Forward()
+ ret <<= self
+ return ret
+
+ def _setResultsName(self, name, list_all_matches=False):
+ if (
+ __diag__.warn_name_set_on_empty_Forward
+ and Diagnostics.warn_name_set_on_empty_Forward
+ not in self.suppress_warnings_
+ ):
+ if self.expr is None:
+ warnings.warn(
+ "{}: setting results name {!r} on {} expression "
+ "that has no contained expression".format(
+ "warn_name_set_on_empty_Forward", name, type(self).__name__
+ ),
+ stacklevel=3,
+ )
+
+ return super()._setResultsName(name, list_all_matches)
+
+ ignoreWhitespace = ignore_whitespace
+ leaveWhitespace = leave_whitespace
+
+
+class TokenConverter(ParseElementEnhance):
+ """
+ Abstract subclass of :class:`ParseExpression`, for converting parsed results.
+ """
+
+ def __init__(self, expr: Union[ParserElement, str], savelist=False):
+ super().__init__(expr) # , savelist)
+ self.saveAsList = False
+
+
+class Combine(TokenConverter):
+ """Converter to concatenate all matching tokens to a single string.
+ By default, the matching patterns must also be contiguous in the
+ input string; this can be disabled by specifying
+ ``'adjacent=False'`` in the constructor.
+
+ Example::
+
+ real = Word(nums) + '.' + Word(nums)
+ print(real.parse_string('3.1416')) # -> ['3', '.', '1416']
+ # will also erroneously match the following
+ print(real.parse_string('3. 1416')) # -> ['3', '.', '1416']
+
+ real = Combine(Word(nums) + '.' + Word(nums))
+ print(real.parse_string('3.1416')) # -> ['3.1416']
+ # no match when there are internal spaces
+ print(real.parse_string('3. 1416')) # -> Exception: Expected W:(0123...)
+ """
+
+ def __init__(
+ self,
+ expr: ParserElement,
+ join_string: str = "",
+ adjacent: bool = True,
+ *,
+ joinString: typing.Optional[str] = None,
+ ):
+ super().__init__(expr)
+ joinString = joinString if joinString is not None else join_string
+ # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself
+ if adjacent:
+ self.leave_whitespace()
+ self.adjacent = adjacent
+ self.skipWhitespace = True
+ self.joinString = joinString
+ self.callPreparse = True
+
+ def ignore(self, other) -> ParserElement:
+ if self.adjacent:
+ ParserElement.ignore(self, other)
+ else:
+ super().ignore(other)
+ return self
+
+ def postParse(self, instring, loc, tokenlist):
+ retToks = tokenlist.copy()
+ del retToks[:]
+ retToks += ParseResults(
+ ["".join(tokenlist._asStringList(self.joinString))], modal=self.modalResults
+ )
+
+ if self.resultsName and retToks.haskeys():
+ return [retToks]
+ else:
+ return retToks
+
+
+class Group(TokenConverter):
+ """Converter to return the matched tokens as a list - useful for
+ returning tokens of :class:`ZeroOrMore` and :class:`OneOrMore` expressions.
+
+ The optional ``aslist`` argument when set to True will return the
+ parsed tokens as a Python list instead of a pyparsing ParseResults.
+
+ Example::
+
+ ident = Word(alphas)
+ num = Word(nums)
+ term = ident | num
+ func = ident + Opt(delimited_list(term))
+ print(func.parse_string("fn a, b, 100"))
+ # -> ['fn', 'a', 'b', '100']
+
+ func = ident + Group(Opt(delimited_list(term)))
+ print(func.parse_string("fn a, b, 100"))
+ # -> ['fn', ['a', 'b', '100']]
+ """
+
+ def __init__(self, expr: ParserElement, aslist: bool = False):
+ super().__init__(expr)
+ self.saveAsList = True
+ self._asPythonList = aslist
+
+ def postParse(self, instring, loc, tokenlist):
+ if self._asPythonList:
+ return ParseResults.List(
+ tokenlist.asList()
+ if isinstance(tokenlist, ParseResults)
+ else list(tokenlist)
+ )
+ else:
+ return [tokenlist]
+
+
+class Dict(TokenConverter):
+ """Converter to return a repetitive expression as a list, but also
+ as a dictionary. Each element can also be referenced using the first
+ token in the expression as its key. Useful for tabular report
+ scraping when the first column can be used as a item key.
+
+ The optional ``asdict`` argument when set to True will return the
+ parsed tokens as a Python dict instead of a pyparsing ParseResults.
+
+ Example::
+
+ data_word = Word(alphas)
+ label = data_word + FollowedBy(':')
+
+ text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
+ attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
+
+ # print attributes as plain groups
+ print(attr_expr[1, ...].parse_string(text).dump())
+
+ # instead of OneOrMore(expr), parse using Dict(Group(expr)[1, ...]) - Dict will auto-assign names
+ result = Dict(Group(attr_expr)[1, ...]).parse_string(text)
+ print(result.dump())
+
+ # access named fields as dict entries, or output as dict
+ print(result['shape'])
+ print(result.as_dict())
+
+ prints::
+
+ ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap']
+ [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
+ - color: 'light blue'
+ - posn: 'upper left'
+ - shape: 'SQUARE'
+ - texture: 'burlap'
+ SQUARE
+ {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'}
+
+ See more examples at :class:`ParseResults` of accessing fields by results name.
+ """
+
+ def __init__(self, expr: ParserElement, asdict: bool = False):
+ super().__init__(expr)
+ self.saveAsList = True
+ self._asPythonDict = asdict
+
+ def postParse(self, instring, loc, tokenlist):
+ for i, tok in enumerate(tokenlist):
+ if len(tok) == 0:
+ continue
+
+ ikey = tok[0]
+ if isinstance(ikey, int):
+ ikey = str(ikey).strip()
+
+ if len(tok) == 1:
+ tokenlist[ikey] = _ParseResultsWithOffset("", i)
+
+ elif len(tok) == 2 and not isinstance(tok[1], ParseResults):
+ tokenlist[ikey] = _ParseResultsWithOffset(tok[1], i)
+
+ else:
+ try:
+ dictvalue = tok.copy() # ParseResults(i)
+ except Exception:
+ exc = TypeError(
+ "could not extract dict values from parsed results"
+ " - Dict expression must contain Grouped expressions"
+ )
+ raise exc from None
+
+ del dictvalue[0]
+
+ if len(dictvalue) != 1 or (
+ isinstance(dictvalue, ParseResults) and dictvalue.haskeys()
+ ):
+ tokenlist[ikey] = _ParseResultsWithOffset(dictvalue, i)
+ else:
+ tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0], i)
+
+ if self._asPythonDict:
+ return [tokenlist.as_dict()] if self.resultsName else tokenlist.as_dict()
+ else:
+ return [tokenlist] if self.resultsName else tokenlist
+
+
+class Suppress(TokenConverter):
+ """Converter for ignoring the results of a parsed expression.
+
+ Example::
+
+ source = "a, b, c,d"
+ wd = Word(alphas)
+ wd_list1 = wd + (',' + wd)[...]
+ print(wd_list1.parse_string(source))
+
+ # often, delimiters that are useful during parsing are just in the
+ # way afterward - use Suppress to keep them out of the parsed output
+ wd_list2 = wd + (Suppress(',') + wd)[...]
+ print(wd_list2.parse_string(source))
+
+ # Skipped text (using '...') can be suppressed as well
+ source = "lead in START relevant text END trailing text"
+ start_marker = Keyword("START")
+ end_marker = Keyword("END")
+ find_body = Suppress(...) + start_marker + ... + end_marker
+ print(find_body.parse_string(source)
+
+ prints::
+
+ ['a', ',', 'b', ',', 'c', ',', 'd']
+ ['a', 'b', 'c', 'd']
+ ['START', 'relevant text ', 'END']
+
+ (See also :class:`delimited_list`.)
+ """
+
+ def __init__(self, expr: Union[ParserElement, str], savelist: bool = False):
+ if expr is ...:
+ expr = _PendingSkip(NoMatch())
+ super().__init__(expr)
+
+ def __add__(self, other) -> "ParserElement":
+ if isinstance(self.expr, _PendingSkip):
+ return Suppress(SkipTo(other)) + other
+ else:
+ return super().__add__(other)
+
+ def __sub__(self, other) -> "ParserElement":
+ if isinstance(self.expr, _PendingSkip):
+ return Suppress(SkipTo(other)) - other
+ else:
+ return super().__sub__(other)
+
+ def postParse(self, instring, loc, tokenlist):
+ return []
+
+ def suppress(self) -> ParserElement:
+ return self
+
+
+def trace_parse_action(f: ParseAction) -> ParseAction:
+ """Decorator for debugging parse actions.
+
+ When the parse action is called, this decorator will print
+ ``">> entering method-name(line:<current_source_line>, <parse_location>, <matched_tokens>)"``.
+ When the parse action completes, the decorator will print
+ ``"<<"`` followed by the returned value, or any exception that the parse action raised.
+
+ Example::
+
+ wd = Word(alphas)
+
+ @trace_parse_action
+ def remove_duplicate_chars(tokens):
+ return ''.join(sorted(set(''.join(tokens))))
+
+ wds = wd[1, ...].set_parse_action(remove_duplicate_chars)
+ print(wds.parse_string("slkdjs sld sldd sdlf sdljf"))
+
+ prints::
+
+ >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {}))
+ <<leaving remove_duplicate_chars (ret: 'dfjkls')
+ ['dfjkls']
+ """
+ f = _trim_arity(f)
+
+ def z(*paArgs):
+ thisFunc = f.__name__
+ s, l, t = paArgs[-3:]
+ if len(paArgs) > 3:
+ thisFunc = paArgs[0].__class__.__name__ + "." + thisFunc
+ sys.stderr.write(
+ ">>entering {}(line: {!r}, {}, {!r})\n".format(thisFunc, line(l, s), l, t)
+ )
+ try:
+ ret = f(*paArgs)
+ except Exception as exc:
+ sys.stderr.write("<<leaving {} (exception: {})\n".format(thisFunc, exc))
+ raise
+ sys.stderr.write("<<leaving {} (ret: {!r})\n".format(thisFunc, ret))
+ return ret
+
+ z.__name__ = f.__name__
+ return z
+
+
+# convenience constants for positional expressions
+empty = Empty().set_name("empty")
+line_start = LineStart().set_name("line_start")
+line_end = LineEnd().set_name("line_end")
+string_start = StringStart().set_name("string_start")
+string_end = StringEnd().set_name("string_end")
+
+_escapedPunc = Word(_bslash, r"\[]-*.$+^?()~ ", exact=2).set_parse_action(
+ lambda s, l, t: t[0][1]
+)
+_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").set_parse_action(
+ lambda s, l, t: chr(int(t[0].lstrip(r"\0x"), 16))
+)
+_escapedOctChar = Regex(r"\\0[0-7]+").set_parse_action(
+ lambda s, l, t: chr(int(t[0][1:], 8))
+)
+_singleChar = (
+ _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r"\]", exact=1)
+)
+_charRange = Group(_singleChar + Suppress("-") + _singleChar)
+_reBracketExpr = (
+ Literal("[")
+ + Opt("^").set_results_name("negate")
+ + Group(OneOrMore(_charRange | _singleChar)).set_results_name("body")
+ + "]"
+)
+
+
+def srange(s: str) -> str:
+ r"""Helper to easily define string ranges for use in :class:`Word`
+ construction. Borrows syntax from regexp ``'[]'`` string range
+ definitions::
+
+ srange("[0-9]") -> "0123456789"
+ srange("[a-z]") -> "abcdefghijklmnopqrstuvwxyz"
+ srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_"
+
+ The input string must be enclosed in []'s, and the returned string
+ is the expanded character set joined into a single string. The
+ values enclosed in the []'s may be:
+
+ - a single character
+ - an escaped character with a leading backslash (such as ``\-``
+ or ``\]``)
+ - an escaped hex character with a leading ``'\x'``
+ (``\x21``, which is a ``'!'`` character) (``\0x##``
+ is also supported for backwards compatibility)
+ - an escaped octal character with a leading ``'\0'``
+ (``\041``, which is a ``'!'`` character)
+ - a range of any of the above, separated by a dash (``'a-z'``,
+ etc.)
+ - any combination of the above (``'aeiouy'``,
+ ``'a-zA-Z0-9_$'``, etc.)
+ """
+ _expanded = (
+ lambda p: p
+ if not isinstance(p, ParseResults)
+ else "".join(chr(c) for c in range(ord(p[0]), ord(p[1]) + 1))
+ )
+ try:
+ return "".join(_expanded(part) for part in _reBracketExpr.parse_string(s).body)
+ except Exception:
+ return ""
+
+
+def token_map(func, *args) -> ParseAction:
+ """Helper to define a parse action by mapping a function to all
+ elements of a :class:`ParseResults` list. If any additional args are passed,
+ they are forwarded to the given function as additional arguments
+ after the token, as in
+ ``hex_integer = Word(hexnums).set_parse_action(token_map(int, 16))``,
+ which will convert the parsed data to an integer using base 16.
+
+ Example (compare the last to example in :class:`ParserElement.transform_string`::
+
+ hex_ints = Word(hexnums)[1, ...].set_parse_action(token_map(int, 16))
+ hex_ints.run_tests('''
+ 00 11 22 aa FF 0a 0d 1a
+ ''')
+
+ upperword = Word(alphas).set_parse_action(token_map(str.upper))
+ upperword[1, ...].run_tests('''
+ my kingdom for a horse
+ ''')
+
+ wd = Word(alphas).set_parse_action(token_map(str.title))
+ wd[1, ...].set_parse_action(' '.join).run_tests('''
+ now is the winter of our discontent made glorious summer by this sun of york
+ ''')
+
+ prints::
+
+ 00 11 22 aa FF 0a 0d 1a
+ [0, 17, 34, 170, 255, 10, 13, 26]
+
+ my kingdom for a horse
+ ['MY', 'KINGDOM', 'FOR', 'A', 'HORSE']
+
+ now is the winter of our discontent made glorious summer by this sun of york
+ ['Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York']
+ """
+
+ def pa(s, l, t):
+ return [func(tokn, *args) for tokn in t]
+
+ func_name = getattr(func, "__name__", getattr(func, "__class__").__name__)
+ pa.__name__ = func_name
+
+ return pa
+
+
+def autoname_elements() -> None:
+ """
+ Utility to simplify mass-naming of parser elements, for
+ generating railroad diagram with named subdiagrams.
+ """
+ for name, var in sys._getframe().f_back.f_locals.items():
+ if isinstance(var, ParserElement) and not var.customName:
+ var.set_name(name)
+
+
+dbl_quoted_string = Combine(
+ Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*') + '"'
+).set_name("string enclosed in double quotes")
+
+sgl_quoted_string = Combine(
+ Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "'"
+).set_name("string enclosed in single quotes")
+
+quoted_string = Combine(
+ Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*') + '"'
+ | Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "'"
+).set_name("quotedString using single or double quotes")
+
+unicode_string = Combine("u" + quoted_string.copy()).set_name("unicode string literal")
+
+
+alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]")
+punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]")
+
+# build list of built-in expressions, for future reference if a global default value
+# gets updated
+_builtin_exprs: List[ParserElement] = [
+ v for v in vars().values() if isinstance(v, ParserElement)
+]
+
+# backward compatibility names
+tokenMap = token_map
+conditionAsParseAction = condition_as_parse_action
+nullDebugAction = null_debug_action
+sglQuotedString = sgl_quoted_string
+dblQuotedString = dbl_quoted_string
+quotedString = quoted_string
+unicodeString = unicode_string
+lineStart = line_start
+lineEnd = line_end
+stringStart = string_start
+stringEnd = string_end
+traceParseAction = trace_parse_action
diff --git a/src/pip/_vendor/pyparsing/diagram/__init__.py b/src/pip/_vendor/pyparsing/diagram/__init__.py
new file mode 100644
index 000000000..1506d66bf
--- /dev/null
+++ b/src/pip/_vendor/pyparsing/diagram/__init__.py
@@ -0,0 +1,642 @@
+import railroad
+from pip._vendor import pyparsing
+import typing
+from typing import (
+ List,
+ NamedTuple,
+ Generic,
+ TypeVar,
+ Dict,
+ Callable,
+ Set,
+ Iterable,
+)
+from jinja2 import Template
+from io import StringIO
+import inspect
+
+
+jinja2_template_source = """\
+<!DOCTYPE html>
+<html>
+<head>
+ {% if not head %}
+ <style type="text/css">
+ .railroad-heading {
+ font-family: monospace;
+ }
+ </style>
+ {% else %}
+ {{ head | safe }}
+ {% endif %}
+</head>
+<body>
+{{ body | safe }}
+{% for diagram in diagrams %}
+ <div class="railroad-group">
+ <h1 class="railroad-heading">{{ diagram.title }}</h1>
+ <div class="railroad-description">{{ diagram.text }}</div>
+ <div class="railroad-svg">
+ {{ diagram.svg }}
+ </div>
+ </div>
+{% endfor %}
+</body>
+</html>
+"""
+
+template = Template(jinja2_template_source)
+
+# Note: ideally this would be a dataclass, but we're supporting Python 3.5+ so we can't do this yet
+NamedDiagram = NamedTuple(
+ "NamedDiagram",
+ [("name", str), ("diagram", typing.Optional[railroad.DiagramItem]), ("index", int)],
+)
+"""
+A simple structure for associating a name with a railroad diagram
+"""
+
+T = TypeVar("T")
+
+
+class EachItem(railroad.Group):
+ """
+ Custom railroad item to compose a:
+ - Group containing a
+ - OneOrMore containing a
+ - Choice of the elements in the Each
+ with the group label indicating that all must be matched
+ """
+
+ all_label = "[ALL]"
+
+ def __init__(self, *items):
+ choice_item = railroad.Choice(len(items) - 1, *items)
+ one_or_more_item = railroad.OneOrMore(item=choice_item)
+ super().__init__(one_or_more_item, label=self.all_label)
+
+
+class AnnotatedItem(railroad.Group):
+ """
+ Simple subclass of Group that creates an annotation label
+ """
+
+ def __init__(self, label: str, item):
+ super().__init__(item=item, label="[{}]".format(label) if label else label)
+
+
+class EditablePartial(Generic[T]):
+ """
+ Acts like a functools.partial, but can be edited. In other words, it represents a type that hasn't yet been
+ constructed.
+ """
+
+ # We need this here because the railroad constructors actually transform the data, so can't be called until the
+ # entire tree is assembled
+
+ def __init__(self, func: Callable[..., T], args: list, kwargs: dict):
+ self.func = func
+ self.args = args
+ self.kwargs = kwargs
+
+ @classmethod
+ def from_call(cls, func: Callable[..., T], *args, **kwargs) -> "EditablePartial[T]":
+ """
+ If you call this function in the same way that you would call the constructor, it will store the arguments
+ as you expect. For example EditablePartial.from_call(Fraction, 1, 3)() == Fraction(1, 3)
+ """
+ return EditablePartial(func=func, args=list(args), kwargs=kwargs)
+
+ @property
+ def name(self):
+ return self.kwargs["name"]
+
+ def __call__(self) -> T:
+ """
+ Evaluate the partial and return the result
+ """
+ args = self.args.copy()
+ kwargs = self.kwargs.copy()
+
+ # This is a helpful hack to allow you to specify varargs parameters (e.g. *args) as keyword args (e.g.
+ # args=['list', 'of', 'things'])
+ arg_spec = inspect.getfullargspec(self.func)
+ if arg_spec.varargs in self.kwargs:
+ args += kwargs.pop(arg_spec.varargs)
+
+ return self.func(*args, **kwargs)
+
+
+def railroad_to_html(diagrams: List[NamedDiagram], **kwargs) -> str:
+ """
+ Given a list of NamedDiagram, produce a single HTML string that visualises those diagrams
+ :params kwargs: kwargs to be passed in to the template
+ """
+ data = []
+ for diagram in diagrams:
+ if diagram.diagram is None:
+ continue
+ io = StringIO()
+ diagram.diagram.writeSvg(io.write)
+ title = diagram.name
+ if diagram.index == 0:
+ title += " (root)"
+ data.append({"title": title, "text": "", "svg": io.getvalue()})
+
+ return template.render(diagrams=data, **kwargs)
+
+
+def resolve_partial(partial: "EditablePartial[T]") -> T:
+ """
+ Recursively resolves a collection of Partials into whatever type they are
+ """
+ if isinstance(partial, EditablePartial):
+ partial.args = resolve_partial(partial.args)
+ partial.kwargs = resolve_partial(partial.kwargs)
+ return partial()
+ elif isinstance(partial, list):
+ return [resolve_partial(x) for x in partial]
+ elif isinstance(partial, dict):
+ return {key: resolve_partial(x) for key, x in partial.items()}
+ else:
+ return partial
+
+
+def to_railroad(
+ element: pyparsing.ParserElement,
+ diagram_kwargs: typing.Optional[dict] = None,
+ vertical: int = 3,
+ show_results_names: bool = False,
+ show_groups: bool = False,
+) -> List[NamedDiagram]:
+ """
+ Convert a pyparsing element tree into a list of diagrams. This is the recommended entrypoint to diagram
+ creation if you want to access the Railroad tree before it is converted to HTML
+ :param element: base element of the parser being diagrammed
+ :param diagram_kwargs: kwargs to pass to the Diagram() constructor
+ :param vertical: (optional) - int - limit at which number of alternatives should be
+ shown vertically instead of horizontally
+ :param show_results_names - bool to indicate whether results name annotations should be
+ included in the diagram
+ :param show_groups - bool to indicate whether groups should be highlighted with an unlabeled
+ surrounding box
+ """
+ # Convert the whole tree underneath the root
+ lookup = ConverterState(diagram_kwargs=diagram_kwargs or {})
+ _to_diagram_element(
+ element,
+ lookup=lookup,
+ parent=None,
+ vertical=vertical,
+ show_results_names=show_results_names,
+ show_groups=show_groups,
+ )
+
+ root_id = id(element)
+ # Convert the root if it hasn't been already
+ if root_id in lookup:
+ if not element.customName:
+ lookup[root_id].name = ""
+ lookup[root_id].mark_for_extraction(root_id, lookup, force=True)
+
+ # Now that we're finished, we can convert from intermediate structures into Railroad elements
+ diags = list(lookup.diagrams.values())
+ if len(diags) > 1:
+ # collapse out duplicate diags with the same name
+ seen = set()
+ deduped_diags = []
+ for d in diags:
+ # don't extract SkipTo elements, they are uninformative as subdiagrams
+ if d.name == "...":
+ continue
+ if d.name is not None and d.name not in seen:
+ seen.add(d.name)
+ deduped_diags.append(d)
+ resolved = [resolve_partial(partial) for partial in deduped_diags]
+ else:
+ # special case - if just one diagram, always display it, even if
+ # it has no name
+ resolved = [resolve_partial(partial) for partial in diags]
+ return sorted(resolved, key=lambda diag: diag.index)
+
+
+def _should_vertical(
+ specification: int, exprs: Iterable[pyparsing.ParserElement]
+) -> bool:
+ """
+ Returns true if we should return a vertical list of elements
+ """
+ if specification is None:
+ return False
+ else:
+ return len(_visible_exprs(exprs)) >= specification
+
+
+class ElementState:
+ """
+ State recorded for an individual pyparsing Element
+ """
+
+ # Note: this should be a dataclass, but we have to support Python 3.5
+ def __init__(
+ self,
+ element: pyparsing.ParserElement,
+ converted: EditablePartial,
+ parent: EditablePartial,
+ number: int,
+ name: str = None,
+ parent_index: typing.Optional[int] = None,
+ ):
+ #: The pyparsing element that this represents
+ self.element: pyparsing.ParserElement = element
+ #: The name of the element
+ self.name: typing.Optional[str] = name
+ #: The output Railroad element in an unconverted state
+ self.converted: EditablePartial = converted
+ #: The parent Railroad element, which we store so that we can extract this if it's duplicated
+ self.parent: EditablePartial = parent
+ #: The order in which we found this element, used for sorting diagrams if this is extracted into a diagram
+ self.number: int = number
+ #: The index of this inside its parent
+ self.parent_index: typing.Optional[int] = parent_index
+ #: If true, we should extract this out into a subdiagram
+ self.extract: bool = False
+ #: If true, all of this element's children have been filled out
+ self.complete: bool = False
+
+ def mark_for_extraction(
+ self, el_id: int, state: "ConverterState", name: str = None, force: bool = False
+ ):
+ """
+ Called when this instance has been seen twice, and thus should eventually be extracted into a sub-diagram
+ :param el_id: id of the element
+ :param state: element/diagram state tracker
+ :param name: name to use for this element's text
+ :param force: If true, force extraction now, regardless of the state of this. Only useful for extracting the
+ root element when we know we're finished
+ """
+ self.extract = True
+
+ # Set the name
+ if not self.name:
+ if name:
+ # Allow forcing a custom name
+ self.name = name
+ elif self.element.customName:
+ self.name = self.element.customName
+ else:
+ self.name = ""
+
+ # Just because this is marked for extraction doesn't mean we can do it yet. We may have to wait for children
+ # to be added
+ # Also, if this is just a string literal etc, don't bother extracting it
+ if force or (self.complete and _worth_extracting(self.element)):
+ state.extract_into_diagram(el_id)
+
+
+class ConverterState:
+ """
+ Stores some state that persists between recursions into the element tree
+ """
+
+ def __init__(self, diagram_kwargs: typing.Optional[dict] = None):
+ #: A dictionary mapping ParserElements to state relating to them
+ self._element_diagram_states: Dict[int, ElementState] = {}
+ #: A dictionary mapping ParserElement IDs to subdiagrams generated from them
+ self.diagrams: Dict[int, EditablePartial[NamedDiagram]] = {}
+ #: The index of the next unnamed element
+ self.unnamed_index: int = 1
+ #: The index of the next element. This is used for sorting
+ self.index: int = 0
+ #: Shared kwargs that are used to customize the construction of diagrams
+ self.diagram_kwargs: dict = diagram_kwargs or {}
+ self.extracted_diagram_names: Set[str] = set()
+
+ def __setitem__(self, key: int, value: ElementState):
+ self._element_diagram_states[key] = value
+
+ def __getitem__(self, key: int) -> ElementState:
+ return self._element_diagram_states[key]
+
+ def __delitem__(self, key: int):
+ del self._element_diagram_states[key]
+
+ def __contains__(self, key: int):
+ return key in self._element_diagram_states
+
+ def generate_unnamed(self) -> int:
+ """
+ Generate a number used in the name of an otherwise unnamed diagram
+ """
+ self.unnamed_index += 1
+ return self.unnamed_index
+
+ def generate_index(self) -> int:
+ """
+ Generate a number used to index a diagram
+ """
+ self.index += 1
+ return self.index
+
+ def extract_into_diagram(self, el_id: int):
+ """
+ Used when we encounter the same token twice in the same tree. When this
+ happens, we replace all instances of that token with a terminal, and
+ create a new subdiagram for the token
+ """
+ position = self[el_id]
+
+ # Replace the original definition of this element with a regular block
+ if position.parent:
+ ret = EditablePartial.from_call(railroad.NonTerminal, text=position.name)
+ if "item" in position.parent.kwargs:
+ position.parent.kwargs["item"] = ret
+ elif "items" in position.parent.kwargs:
+ position.parent.kwargs["items"][position.parent_index] = ret
+
+ # If the element we're extracting is a group, skip to its content but keep the title
+ if position.converted.func == railroad.Group:
+ content = position.converted.kwargs["item"]
+ else:
+ content = position.converted
+
+ self.diagrams[el_id] = EditablePartial.from_call(
+ NamedDiagram,
+ name=position.name,
+ diagram=EditablePartial.from_call(
+ railroad.Diagram, content, **self.diagram_kwargs
+ ),
+ index=position.number,
+ )
+
+ del self[el_id]
+
+
+def _worth_extracting(element: pyparsing.ParserElement) -> bool:
+ """
+ Returns true if this element is worth having its own sub-diagram. Simply, if any of its children
+ themselves have children, then its complex enough to extract
+ """
+ children = element.recurse()
+ return any(child.recurse() for child in children)
+
+
+def _apply_diagram_item_enhancements(fn):
+ """
+ decorator to ensure enhancements to a diagram item (such as results name annotations)
+ get applied on return from _to_diagram_element (we do this since there are several
+ returns in _to_diagram_element)
+ """
+
+ def _inner(
+ element: pyparsing.ParserElement,
+ parent: typing.Optional[EditablePartial],
+ lookup: ConverterState = None,
+ vertical: int = None,
+ index: int = 0,
+ name_hint: str = None,
+ show_results_names: bool = False,
+ show_groups: bool = False,
+ ) -> typing.Optional[EditablePartial]:
+
+ ret = fn(
+ element,
+ parent,
+ lookup,
+ vertical,
+ index,
+ name_hint,
+ show_results_names,
+ show_groups,
+ )
+
+ # apply annotation for results name, if present
+ if show_results_names and ret is not None:
+ element_results_name = element.resultsName
+ if element_results_name:
+ # add "*" to indicate if this is a "list all results" name
+ element_results_name += "" if element.modalResults else "*"
+ ret = EditablePartial.from_call(
+ railroad.Group, item=ret, label=element_results_name
+ )
+
+ return ret
+
+ return _inner
+
+
+def _visible_exprs(exprs: Iterable[pyparsing.ParserElement]):
+ non_diagramming_exprs = (
+ pyparsing.ParseElementEnhance,
+ pyparsing.PositionToken,
+ pyparsing.And._ErrorStop,
+ )
+ return [
+ e
+ for e in exprs
+ if not (e.customName or e.resultsName or isinstance(e, non_diagramming_exprs))
+ ]
+
+
+@_apply_diagram_item_enhancements
+def _to_diagram_element(
+ element: pyparsing.ParserElement,
+ parent: typing.Optional[EditablePartial],
+ lookup: ConverterState = None,
+ vertical: int = None,
+ index: int = 0,
+ name_hint: str = None,
+ show_results_names: bool = False,
+ show_groups: bool = False,
+) -> typing.Optional[EditablePartial]:
+ """
+ Recursively converts a PyParsing Element to a railroad Element
+ :param lookup: The shared converter state that keeps track of useful things
+ :param index: The index of this element within the parent
+ :param parent: The parent of this element in the output tree
+ :param vertical: Controls at what point we make a list of elements vertical. If this is an integer (the default),
+ it sets the threshold of the number of items before we go vertical. If True, always go vertical, if False, never
+ do so
+ :param name_hint: If provided, this will override the generated name
+ :param show_results_names: bool flag indicating whether to add annotations for results names
+ :returns: The converted version of the input element, but as a Partial that hasn't yet been constructed
+ :param show_groups: bool flag indicating whether to show groups using bounding box
+ """
+ exprs = element.recurse()
+ name = name_hint or element.customName or element.__class__.__name__
+
+ # Python's id() is used to provide a unique identifier for elements
+ el_id = id(element)
+
+ element_results_name = element.resultsName
+
+ # Here we basically bypass processing certain wrapper elements if they contribute nothing to the diagram
+ if not element.customName:
+ if isinstance(
+ element,
+ (
+ # pyparsing.TokenConverter,
+ # pyparsing.Forward,
+ pyparsing.Located,
+ ),
+ ):
+ # However, if this element has a useful custom name, and its child does not, we can pass it on to the child
+ if exprs:
+ if not exprs[0].customName:
+ propagated_name = name
+ else:
+ propagated_name = None
+
+ return _to_diagram_element(
+ element.expr,
+ parent=parent,
+ lookup=lookup,
+ vertical=vertical,
+ index=index,
+ name_hint=propagated_name,
+ show_results_names=show_results_names,
+ show_groups=show_groups,
+ )
+
+ # If the element isn't worth extracting, we always treat it as the first time we say it
+ if _worth_extracting(element):
+ if el_id in lookup:
+ # If we've seen this element exactly once before, we are only just now finding out that it's a duplicate,
+ # so we have to extract it into a new diagram.
+ looked_up = lookup[el_id]
+ looked_up.mark_for_extraction(el_id, lookup, name=name_hint)
+ ret = EditablePartial.from_call(railroad.NonTerminal, text=looked_up.name)
+ return ret
+
+ elif el_id in lookup.diagrams:
+ # If we have seen the element at least twice before, and have already extracted it into a subdiagram, we
+ # just put in a marker element that refers to the sub-diagram
+ ret = EditablePartial.from_call(
+ railroad.NonTerminal, text=lookup.diagrams[el_id].kwargs["name"]
+ )
+ return ret
+
+ # Recursively convert child elements
+ # Here we find the most relevant Railroad element for matching pyparsing Element
+ # We use ``items=[]`` here to hold the place for where the child elements will go once created
+ if isinstance(element, pyparsing.And):
+ # detect And's created with ``expr*N`` notation - for these use a OneOrMore with a repeat
+ # (all will have the same name, and resultsName)
+ if not exprs:
+ return None
+ if len(set((e.name, e.resultsName) for e in exprs)) == 1:
+ ret = EditablePartial.from_call(
+ railroad.OneOrMore, item="", repeat=str(len(exprs))
+ )
+ elif _should_vertical(vertical, exprs):
+ ret = EditablePartial.from_call(railroad.Stack, items=[])
+ else:
+ ret = EditablePartial.from_call(railroad.Sequence, items=[])
+ elif isinstance(element, (pyparsing.Or, pyparsing.MatchFirst)):
+ if not exprs:
+ return None
+ if _should_vertical(vertical, exprs):
+ ret = EditablePartial.from_call(railroad.Choice, 0, items=[])
+ else:
+ ret = EditablePartial.from_call(railroad.HorizontalChoice, items=[])
+ elif isinstance(element, pyparsing.Each):
+ if not exprs:
+ return None
+ ret = EditablePartial.from_call(EachItem, items=[])
+ elif isinstance(element, pyparsing.NotAny):
+ ret = EditablePartial.from_call(AnnotatedItem, label="NOT", item="")
+ elif isinstance(element, pyparsing.FollowedBy):
+ ret = EditablePartial.from_call(AnnotatedItem, label="LOOKAHEAD", item="")
+ elif isinstance(element, pyparsing.PrecededBy):
+ ret = EditablePartial.from_call(AnnotatedItem, label="LOOKBEHIND", item="")
+ elif isinstance(element, pyparsing.Group):
+ if show_groups:
+ ret = EditablePartial.from_call(AnnotatedItem, label="", item="")
+ else:
+ ret = EditablePartial.from_call(railroad.Group, label="", item="")
+ elif isinstance(element, pyparsing.TokenConverter):
+ ret = EditablePartial.from_call(
+ AnnotatedItem, label=type(element).__name__.lower(), item=""
+ )
+ elif isinstance(element, pyparsing.Opt):
+ ret = EditablePartial.from_call(railroad.Optional, item="")
+ elif isinstance(element, pyparsing.OneOrMore):
+ ret = EditablePartial.from_call(railroad.OneOrMore, item="")
+ elif isinstance(element, pyparsing.ZeroOrMore):
+ ret = EditablePartial.from_call(railroad.ZeroOrMore, item="")
+ elif isinstance(element, pyparsing.Group):
+ ret = EditablePartial.from_call(
+ railroad.Group, item=None, label=element_results_name
+ )
+ elif isinstance(element, pyparsing.Empty) and not element.customName:
+ # Skip unnamed "Empty" elements
+ ret = None
+ elif len(exprs) > 1:
+ ret = EditablePartial.from_call(railroad.Sequence, items=[])
+ elif len(exprs) > 0 and not element_results_name:
+ ret = EditablePartial.from_call(railroad.Group, item="", label=name)
+ else:
+ terminal = EditablePartial.from_call(railroad.Terminal, element.defaultName)
+ ret = terminal
+
+ if ret is None:
+ return
+
+ # Indicate this element's position in the tree so we can extract it if necessary
+ lookup[el_id] = ElementState(
+ element=element,
+ converted=ret,
+ parent=parent,
+ parent_index=index,
+ number=lookup.generate_index(),
+ )
+ if element.customName:
+ lookup[el_id].mark_for_extraction(el_id, lookup, element.customName)
+
+ i = 0
+ for expr in exprs:
+ # Add a placeholder index in case we have to extract the child before we even add it to the parent
+ if "items" in ret.kwargs:
+ ret.kwargs["items"].insert(i, None)
+
+ item = _to_diagram_element(
+ expr,
+ parent=ret,
+ lookup=lookup,
+ vertical=vertical,
+ index=i,
+ show_results_names=show_results_names,
+ show_groups=show_groups,
+ )
+
+ # Some elements don't need to be shown in the diagram
+ if item is not None:
+ if "item" in ret.kwargs:
+ ret.kwargs["item"] = item
+ elif "items" in ret.kwargs:
+ # If we've already extracted the child, don't touch this index, since it's occupied by a nonterminal
+ ret.kwargs["items"][i] = item
+ i += 1
+ elif "items" in ret.kwargs:
+ # If we're supposed to skip this element, remove it from the parent
+ del ret.kwargs["items"][i]
+
+ # If all this items children are none, skip this item
+ if ret and (
+ ("items" in ret.kwargs and len(ret.kwargs["items"]) == 0)
+ or ("item" in ret.kwargs and ret.kwargs["item"] is None)
+ ):
+ ret = EditablePartial.from_call(railroad.Terminal, name)
+
+ # Mark this element as "complete", ie it has all of its children
+ if el_id in lookup:
+ lookup[el_id].complete = True
+
+ if el_id in lookup and lookup[el_id].extract and lookup[el_id].complete:
+ lookup.extract_into_diagram(el_id)
+ if ret is not None:
+ ret = EditablePartial.from_call(
+ railroad.NonTerminal, text=lookup.diagrams[el_id].kwargs["name"]
+ )
+
+ return ret
diff --git a/src/pip/_vendor/pyparsing/exceptions.py b/src/pip/_vendor/pyparsing/exceptions.py
new file mode 100644
index 000000000..a38447bb0
--- /dev/null
+++ b/src/pip/_vendor/pyparsing/exceptions.py
@@ -0,0 +1,267 @@
+# exceptions.py
+
+import re
+import sys
+import typing
+
+from .util import col, line, lineno, _collapse_string_to_ranges
+from .unicode import pyparsing_unicode as ppu
+
+
+class ExceptionWordUnicode(ppu.Latin1, ppu.LatinA, ppu.LatinB, ppu.Greek, ppu.Cyrillic):
+ pass
+
+
+_extract_alphanums = _collapse_string_to_ranges(ExceptionWordUnicode.alphanums)
+_exception_word_extractor = re.compile("([" + _extract_alphanums + "]{1,16})|.")
+
+
+class ParseBaseException(Exception):
+ """base exception class for all parsing runtime exceptions"""
+
+ # Performance tuning: we construct a *lot* of these, so keep this
+ # constructor as small and fast as possible
+ def __init__(
+ self,
+ pstr: str,
+ loc: int = 0,
+ msg: typing.Optional[str] = None,
+ elem=None,
+ ):
+ self.loc = loc
+ if msg is None:
+ self.msg = pstr
+ self.pstr = ""
+ else:
+ self.msg = msg
+ self.pstr = pstr
+ self.parser_element = self.parserElement = elem
+ self.args = (pstr, loc, msg)
+
+ @staticmethod
+ def explain_exception(exc, depth=16):
+ """
+ Method to take an exception and translate the Python internal traceback into a list
+ of the pyparsing expressions that caused the exception to be raised.
+
+ Parameters:
+
+ - exc - exception raised during parsing (need not be a ParseException, in support
+ of Python exceptions that might be raised in a parse action)
+ - depth (default=16) - number of levels back in the stack trace to list expression
+ and function names; if None, the full stack trace names will be listed; if 0, only
+ the failing input line, marker, and exception string will be shown
+
+ Returns a multi-line string listing the ParserElements and/or function names in the
+ exception's stack trace.
+ """
+ import inspect
+ from .core import ParserElement
+
+ if depth is None:
+ depth = sys.getrecursionlimit()
+ ret = []
+ if isinstance(exc, ParseBaseException):
+ ret.append(exc.line)
+ ret.append(" " * (exc.column - 1) + "^")
+ ret.append("{}: {}".format(type(exc).__name__, exc))
+
+ if depth > 0:
+ callers = inspect.getinnerframes(exc.__traceback__, context=depth)
+ seen = set()
+ for i, ff in enumerate(callers[-depth:]):
+ frm = ff[0]
+
+ f_self = frm.f_locals.get("self", None)
+ if isinstance(f_self, ParserElement):
+ if frm.f_code.co_name not in ("parseImpl", "_parseNoCache"):
+ continue
+ if id(f_self) in seen:
+ continue
+ seen.add(id(f_self))
+
+ self_type = type(f_self)
+ ret.append(
+ "{}.{} - {}".format(
+ self_type.__module__, self_type.__name__, f_self
+ )
+ )
+
+ elif f_self is not None:
+ self_type = type(f_self)
+ ret.append("{}.{}".format(self_type.__module__, self_type.__name__))
+
+ else:
+ code = frm.f_code
+ if code.co_name in ("wrapper", "<module>"):
+ continue
+
+ ret.append("{}".format(code.co_name))
+
+ depth -= 1
+ if not depth:
+ break
+
+ return "\n".join(ret)
+
+ @classmethod
+ def _from_exception(cls, pe):
+ """
+ internal factory method to simplify creating one type of ParseException
+ from another - avoids having __init__ signature conflicts among subclasses
+ """
+ return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement)
+
+ @property
+ def line(self) -> str:
+ """
+ Return the line of text where the exception occurred.
+ """
+ return line(self.loc, self.pstr)
+
+ @property
+ def lineno(self) -> int:
+ """
+ Return the 1-based line number of text where the exception occurred.
+ """
+ return lineno(self.loc, self.pstr)
+
+ @property
+ def col(self) -> int:
+ """
+ Return the 1-based column on the line of text where the exception occurred.
+ """
+ return col(self.loc, self.pstr)
+
+ @property
+ def column(self) -> int:
+ """
+ Return the 1-based column on the line of text where the exception occurred.
+ """
+ return col(self.loc, self.pstr)
+
+ def __str__(self) -> str:
+ if self.pstr:
+ if self.loc >= len(self.pstr):
+ foundstr = ", found end of text"
+ else:
+ # pull out next word at error location
+ found_match = _exception_word_extractor.match(self.pstr, self.loc)
+ if found_match is not None:
+ found = found_match.group(0)
+ else:
+ found = self.pstr[self.loc : self.loc + 1]
+ foundstr = (", found %r" % found).replace(r"\\", "\\")
+ else:
+ foundstr = ""
+ return "{}{} (at char {}), (line:{}, col:{})".format(
+ self.msg, foundstr, self.loc, self.lineno, self.column
+ )
+
+ def __repr__(self):
+ return str(self)
+
+ def mark_input_line(self, marker_string: str = None, *, markerString=">!<") -> str:
+ """
+ Extracts the exception line from the input string, and marks
+ the location of the exception with a special symbol.
+ """
+ markerString = marker_string if marker_string is not None else markerString
+ line_str = self.line
+ line_column = self.column - 1
+ if markerString:
+ line_str = "".join(
+ (line_str[:line_column], markerString, line_str[line_column:])
+ )
+ return line_str.strip()
+
+ def explain(self, depth=16) -> str:
+ """
+ Method to translate the Python internal traceback into a list
+ of the pyparsing expressions that caused the exception to be raised.
+
+ Parameters:
+
+ - depth (default=16) - number of levels back in the stack trace to list expression
+ and function names; if None, the full stack trace names will be listed; if 0, only
+ the failing input line, marker, and exception string will be shown
+
+ Returns a multi-line string listing the ParserElements and/or function names in the
+ exception's stack trace.
+
+ Example::
+
+ expr = pp.Word(pp.nums) * 3
+ try:
+ expr.parse_string("123 456 A789")
+ except pp.ParseException as pe:
+ print(pe.explain(depth=0))
+
+ prints::
+
+ 123 456 A789
+ ^
+ ParseException: Expected W:(0-9), found 'A' (at char 8), (line:1, col:9)
+
+ Note: the diagnostic output will include string representations of the expressions
+ that failed to parse. These representations will be more helpful if you use `set_name` to
+ give identifiable names to your expressions. Otherwise they will use the default string
+ forms, which may be cryptic to read.
+
+ Note: pyparsing's default truncation of exception tracebacks may also truncate the
+ stack of expressions that are displayed in the ``explain`` output. To get the full listing
+ of parser expressions, you may have to set ``ParserElement.verbose_stacktrace = True``
+ """
+ return self.explain_exception(self, depth)
+
+ markInputline = mark_input_line
+
+
+class ParseException(ParseBaseException):
+ """
+ Exception thrown when a parse expression doesn't match the input string
+
+ Example::
+
+ try:
+ Word(nums).set_name("integer").parse_string("ABC")
+ except ParseException as pe:
+ print(pe)
+ print("column: {}".format(pe.column))
+
+ prints::
+
+ Expected integer (at char 0), (line:1, col:1)
+ column: 1
+
+ """
+
+
+class ParseFatalException(ParseBaseException):
+ """
+ User-throwable exception thrown when inconsistent parse content
+ is found; stops all parsing immediately
+ """
+
+
+class ParseSyntaxException(ParseFatalException):
+ """
+ Just like :class:`ParseFatalException`, but thrown internally
+ when an :class:`ErrorStop<And._ErrorStop>` ('-' operator) indicates
+ that parsing is to stop immediately because an unbacktrackable
+ syntax error has been found.
+ """
+
+
+class RecursiveGrammarException(Exception):
+ """
+ Exception thrown by :class:`ParserElement.validate` if the
+ grammar could be left-recursive; parser may need to enable
+ left recursion using :class:`ParserElement.enable_left_recursion<ParserElement.enable_left_recursion>`
+ """
+
+ def __init__(self, parseElementList):
+ self.parseElementTrace = parseElementList
+
+ def __str__(self) -> str:
+ return "RecursiveGrammarException: {}".format(self.parseElementTrace)
diff --git a/src/pip/_vendor/pyparsing/helpers.py b/src/pip/_vendor/pyparsing/helpers.py
new file mode 100644
index 000000000..9588b3b78
--- /dev/null
+++ b/src/pip/_vendor/pyparsing/helpers.py
@@ -0,0 +1,1088 @@
+# helpers.py
+import html.entities
+import re
+import typing
+
+from . import __diag__
+from .core import *
+from .util import _bslash, _flatten, _escape_regex_range_chars
+
+
+#
+# global helpers
+#
+def delimited_list(
+ expr: Union[str, ParserElement],
+ delim: Union[str, ParserElement] = ",",
+ combine: bool = False,
+ min: typing.Optional[int] = None,
+ max: typing.Optional[int] = None,
+ *,
+ allow_trailing_delim: bool = False,
+) -> ParserElement:
+ """Helper to define a delimited list of expressions - the delimiter
+ defaults to ','. By default, the list elements and delimiters can
+ have intervening whitespace, and comments, but this can be
+ overridden by passing ``combine=True`` in the constructor. If
+ ``combine`` is set to ``True``, the matching tokens are
+ returned as a single token string, with the delimiters included;
+ otherwise, the matching tokens are returned as a list of tokens,
+ with the delimiters suppressed.
+
+ If ``allow_trailing_delim`` is set to True, then the list may end with
+ a delimiter.
+
+ Example::
+
+ delimited_list(Word(alphas)).parse_string("aa,bb,cc") # -> ['aa', 'bb', 'cc']
+ delimited_list(Word(hexnums), delim=':', combine=True).parse_string("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE']
+ """
+ if isinstance(expr, str_type):
+ expr = ParserElement._literalStringClass(expr)
+
+ dlName = "{expr} [{delim} {expr}]...{end}".format(
+ expr=str(expr.copy().streamline()),
+ delim=str(delim),
+ end=" [{}]".format(str(delim)) if allow_trailing_delim else "",
+ )
+
+ if not combine:
+ delim = Suppress(delim)
+
+ if min is not None:
+ if min < 1:
+ raise ValueError("min must be greater than 0")
+ min -= 1
+ if max is not None:
+ if min is not None and max <= min:
+ raise ValueError("max must be greater than, or equal to min")
+ max -= 1
+ delimited_list_expr = expr + (delim + expr)[min, max]
+
+ if allow_trailing_delim:
+ delimited_list_expr += Opt(delim)
+
+ if combine:
+ return Combine(delimited_list_expr).set_name(dlName)
+ else:
+ return delimited_list_expr.set_name(dlName)
+
+
+def counted_array(
+ expr: ParserElement,
+ int_expr: typing.Optional[ParserElement] = None,
+ *,
+ intExpr: typing.Optional[ParserElement] = None,
+) -> ParserElement:
+ """Helper to define a counted list of expressions.
+
+ This helper defines a pattern of the form::
+
+ integer expr expr expr...
+
+ where the leading integer tells how many expr expressions follow.
+ The matched tokens returns the array of expr tokens as a list - the
+ leading count token is suppressed.
+
+ If ``int_expr`` is specified, it should be a pyparsing expression
+ that produces an integer value.
+
+ Example::
+
+ counted_array(Word(alphas)).parse_string('2 ab cd ef') # -> ['ab', 'cd']
+
+ # in this parser, the leading integer value is given in binary,
+ # '10' indicating that 2 values are in the array
+ binary_constant = Word('01').set_parse_action(lambda t: int(t[0], 2))
+ counted_array(Word(alphas), int_expr=binary_constant).parse_string('10 ab cd ef') # -> ['ab', 'cd']
+
+ # if other fields must be parsed after the count but before the
+ # list items, give the fields results names and they will
+ # be preserved in the returned ParseResults:
+ count_with_metadata = integer + Word(alphas)("type")
+ typed_array = counted_array(Word(alphanums), int_expr=count_with_metadata)("items")
+ result = typed_array.parse_string("3 bool True True False")
+ print(result.dump())
+
+ # prints
+ # ['True', 'True', 'False']
+ # - items: ['True', 'True', 'False']
+ # - type: 'bool'
+ """
+ intExpr = intExpr or int_expr
+ array_expr = Forward()
+
+ def count_field_parse_action(s, l, t):
+ nonlocal array_expr
+ n = t[0]
+ array_expr <<= (expr * n) if n else Empty()
+ # clear list contents, but keep any named results
+ del t[:]
+
+ if intExpr is None:
+ intExpr = Word(nums).set_parse_action(lambda t: int(t[0]))
+ else:
+ intExpr = intExpr.copy()
+ intExpr.set_name("arrayLen")
+ intExpr.add_parse_action(count_field_parse_action, call_during_try=True)
+ return (intExpr + array_expr).set_name("(len) " + str(expr) + "...")
+
+
+def match_previous_literal(expr: ParserElement) -> ParserElement:
+ """Helper to define an expression that is indirectly defined from
+ the tokens matched in a previous expression, that is, it looks for
+ a 'repeat' of a previous expression. For example::
+
+ first = Word(nums)
+ second = match_previous_literal(first)
+ match_expr = first + ":" + second
+
+ will match ``"1:1"``, but not ``"1:2"``. Because this
+ matches a previous literal, will also match the leading
+ ``"1:1"`` in ``"1:10"``. If this is not desired, use
+ :class:`match_previous_expr`. Do *not* use with packrat parsing
+ enabled.
+ """
+ rep = Forward()
+
+ def copy_token_to_repeater(s, l, t):
+ if t:
+ if len(t) == 1:
+ rep << t[0]
+ else:
+ # flatten t tokens
+ tflat = _flatten(t.as_list())
+ rep << And(Literal(tt) for tt in tflat)
+ else:
+ rep << Empty()
+
+ expr.add_parse_action(copy_token_to_repeater, callDuringTry=True)
+ rep.set_name("(prev) " + str(expr))
+ return rep
+
+
+def match_previous_expr(expr: ParserElement) -> ParserElement:
+ """Helper to define an expression that is indirectly defined from
+ the tokens matched in a previous expression, that is, it looks for
+ a 'repeat' of a previous expression. For example::
+
+ first = Word(nums)
+ second = match_previous_expr(first)
+ match_expr = first + ":" + second
+
+ will match ``"1:1"``, but not ``"1:2"``. Because this
+ matches by expressions, will *not* match the leading ``"1:1"``
+ in ``"1:10"``; the expressions are evaluated first, and then
+ compared, so ``"1"`` is compared with ``"10"``. Do *not* use
+ with packrat parsing enabled.
+ """
+ rep = Forward()
+ e2 = expr.copy()
+ rep <<= e2
+
+ def copy_token_to_repeater(s, l, t):
+ matchTokens = _flatten(t.as_list())
+
+ def must_match_these_tokens(s, l, t):
+ theseTokens = _flatten(t.as_list())
+ if theseTokens != matchTokens:
+ raise ParseException(
+ s, l, "Expected {}, found{}".format(matchTokens, theseTokens)
+ )
+
+ rep.set_parse_action(must_match_these_tokens, callDuringTry=True)
+
+ expr.add_parse_action(copy_token_to_repeater, callDuringTry=True)
+ rep.set_name("(prev) " + str(expr))
+ return rep
+
+
+def one_of(
+ strs: Union[typing.Iterable[str], str],
+ caseless: bool = False,
+ use_regex: bool = True,
+ as_keyword: bool = False,
+ *,
+ useRegex: bool = True,
+ asKeyword: bool = False,
+) -> ParserElement:
+ """Helper to quickly define a set of alternative :class:`Literal` s,
+ and makes sure to do longest-first testing when there is a conflict,
+ regardless of the input order, but returns
+ a :class:`MatchFirst` for best performance.
+
+ Parameters:
+
+ - ``strs`` - a string of space-delimited literals, or a collection of
+ string literals
+ - ``caseless`` - treat all literals as caseless - (default= ``False``)
+ - ``use_regex`` - as an optimization, will
+ generate a :class:`Regex` object; otherwise, will generate
+ a :class:`MatchFirst` object (if ``caseless=True`` or ``asKeyword=True``, or if
+ creating a :class:`Regex` raises an exception) - (default= ``True``)
+ - ``as_keyword`` - enforce :class:`Keyword`-style matching on the
+ generated expressions - (default= ``False``)
+ - ``asKeyword`` and ``useRegex`` are retained for pre-PEP8 compatibility,
+ but will be removed in a future release
+
+ Example::
+
+ comp_oper = one_of("< = > <= >= !=")
+ var = Word(alphas)
+ number = Word(nums)
+ term = var | number
+ comparison_expr = term + comp_oper + term
+ print(comparison_expr.search_string("B = 12 AA=23 B<=AA AA>12"))
+
+ prints::
+
+ [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']]
+ """
+ asKeyword = asKeyword or as_keyword
+ useRegex = useRegex and use_regex
+
+ if (
+ isinstance(caseless, str_type)
+ and __diag__.warn_on_multiple_string_args_to_oneof
+ ):
+ warnings.warn(
+ "More than one string argument passed to one_of, pass"
+ " choices as a list or space-delimited string",
+ stacklevel=2,
+ )
+
+ if caseless:
+ isequal = lambda a, b: a.upper() == b.upper()
+ masks = lambda a, b: b.upper().startswith(a.upper())
+ parseElementClass = CaselessKeyword if asKeyword else CaselessLiteral
+ else:
+ isequal = lambda a, b: a == b
+ masks = lambda a, b: b.startswith(a)
+ parseElementClass = Keyword if asKeyword else Literal
+
+ symbols: List[str] = []
+ if isinstance(strs, str_type):
+ symbols = strs.split()
+ elif isinstance(strs, Iterable):
+ symbols = list(strs)
+ else:
+ raise TypeError("Invalid argument to one_of, expected string or iterable")
+ if not symbols:
+ return NoMatch()
+
+ # reorder given symbols to take care to avoid masking longer choices with shorter ones
+ # (but only if the given symbols are not just single characters)
+ if any(len(sym) > 1 for sym in symbols):
+ i = 0
+ while i < len(symbols) - 1:
+ cur = symbols[i]
+ for j, other in enumerate(symbols[i + 1 :]):
+ if isequal(other, cur):
+ del symbols[i + j + 1]
+ break
+ elif masks(cur, other):
+ del symbols[i + j + 1]
+ symbols.insert(i, other)
+ break
+ else:
+ i += 1
+
+ if useRegex:
+ re_flags: int = re.IGNORECASE if caseless else 0
+
+ try:
+ if all(len(sym) == 1 for sym in symbols):
+ # symbols are just single characters, create range regex pattern
+ patt = "[{}]".format(
+ "".join(_escape_regex_range_chars(sym) for sym in symbols)
+ )
+ else:
+ patt = "|".join(re.escape(sym) for sym in symbols)
+
+ # wrap with \b word break markers if defining as keywords
+ if asKeyword:
+ patt = r"\b(?:{})\b".format(patt)
+
+ ret = Regex(patt, flags=re_flags).set_name(" | ".join(symbols))
+
+ if caseless:
+ # add parse action to return symbols as specified, not in random
+ # casing as found in input string
+ symbol_map = {sym.lower(): sym for sym in symbols}
+ ret.add_parse_action(lambda s, l, t: symbol_map[t[0].lower()])
+
+ return ret
+
+ except re.error:
+ warnings.warn(
+ "Exception creating Regex for one_of, building MatchFirst", stacklevel=2
+ )
+
+ # last resort, just use MatchFirst
+ return MatchFirst(parseElementClass(sym) for sym in symbols).set_name(
+ " | ".join(symbols)
+ )
+
+
+def dict_of(key: ParserElement, value: ParserElement) -> ParserElement:
+ """Helper to easily and clearly define a dictionary by specifying
+ the respective patterns for the key and value. Takes care of
+ defining the :class:`Dict`, :class:`ZeroOrMore`, and
+ :class:`Group` tokens in the proper order. The key pattern
+ can include delimiting markers or punctuation, as long as they are
+ suppressed, thereby leaving the significant key text. The value
+ pattern can include named results, so that the :class:`Dict` results
+ can include named token fields.
+
+ Example::
+
+ text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
+ attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
+ print(attr_expr[1, ...].parse_string(text).dump())
+
+ attr_label = label
+ attr_value = Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)
+
+ # similar to Dict, but simpler call format
+ result = dict_of(attr_label, attr_value).parse_string(text)
+ print(result.dump())
+ print(result['shape'])
+ print(result.shape) # object attribute access works too
+ print(result.as_dict())
+
+ prints::
+
+ [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
+ - color: 'light blue'
+ - posn: 'upper left'
+ - shape: 'SQUARE'
+ - texture: 'burlap'
+ SQUARE
+ SQUARE
+ {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'}
+ """
+ return Dict(OneOrMore(Group(key + value)))
+
+
+def original_text_for(
+ expr: ParserElement, as_string: bool = True, *, asString: bool = True
+) -> ParserElement:
+ """Helper to return the original, untokenized text for a given
+ expression. Useful to restore the parsed fields of an HTML start
+ tag into the raw tag text itself, or to revert separate tokens with
+ intervening whitespace back to the original matching input text. By
+ default, returns astring containing the original parsed text.
+
+ If the optional ``as_string`` argument is passed as
+ ``False``, then the return value is
+ a :class:`ParseResults` containing any results names that
+ were originally matched, and a single token containing the original
+ matched text from the input string. So if the expression passed to
+ :class:`original_text_for` contains expressions with defined
+ results names, you must set ``as_string`` to ``False`` if you
+ want to preserve those results name values.
+
+ The ``asString`` pre-PEP8 argument is retained for compatibility,
+ but will be removed in a future release.
+
+ Example::
+
+ src = "this is test <b> bold <i>text</i> </b> normal text "
+ for tag in ("b", "i"):
+ opener, closer = make_html_tags(tag)
+ patt = original_text_for(opener + SkipTo(closer) + closer)
+ print(patt.search_string(src)[0])
+
+ prints::
+
+ ['<b> bold <i>text</i> </b>']
+ ['<i>text</i>']
+ """
+ asString = asString and as_string
+
+ locMarker = Empty().set_parse_action(lambda s, loc, t: loc)
+ endlocMarker = locMarker.copy()
+ endlocMarker.callPreparse = False
+ matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end")
+ if asString:
+ extractText = lambda s, l, t: s[t._original_start : t._original_end]
+ else:
+
+ def extractText(s, l, t):
+ t[:] = [s[t.pop("_original_start") : t.pop("_original_end")]]
+
+ matchExpr.set_parse_action(extractText)
+ matchExpr.ignoreExprs = expr.ignoreExprs
+ matchExpr.suppress_warning(Diagnostics.warn_ungrouped_named_tokens_in_collection)
+ return matchExpr
+
+
+def ungroup(expr: ParserElement) -> ParserElement:
+ """Helper to undo pyparsing's default grouping of And expressions,
+ even if all but one are non-empty.
+ """
+ return TokenConverter(expr).add_parse_action(lambda t: t[0])
+
+
+def locatedExpr(expr: ParserElement) -> ParserElement:
+ """
+ (DEPRECATED - future code should use the Located class)
+ Helper to decorate a returned token with its starting and ending
+ locations in the input string.
+
+ This helper adds the following results names:
+
+ - ``locn_start`` - location where matched expression begins
+ - ``locn_end`` - location where matched expression ends
+ - ``value`` - the actual parsed results
+
+ Be careful if the input text contains ``<TAB>`` characters, you
+ may want to call :class:`ParserElement.parseWithTabs`
+
+ Example::
+
+ wd = Word(alphas)
+ for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"):
+ print(match)
+
+ prints::
+
+ [[0, 'ljsdf', 5]]
+ [[8, 'lksdjjf', 15]]
+ [[18, 'lkkjj', 23]]
+ """
+ locator = Empty().set_parse_action(lambda ss, ll, tt: ll)
+ return Group(
+ locator("locn_start")
+ + expr("value")
+ + locator.copy().leaveWhitespace()("locn_end")
+ )
+
+
+def nested_expr(
+ opener: Union[str, ParserElement] = "(",
+ closer: Union[str, ParserElement] = ")",
+ content: typing.Optional[ParserElement] = None,
+ ignore_expr: ParserElement = quoted_string(),
+ *,
+ ignoreExpr: ParserElement = quoted_string(),
+) -> ParserElement:
+ """Helper method for defining nested lists enclosed in opening and
+ closing delimiters (``"("`` and ``")"`` are the default).
+
+ Parameters:
+ - ``opener`` - opening character for a nested list
+ (default= ``"("``); can also be a pyparsing expression
+ - ``closer`` - closing character for a nested list
+ (default= ``")"``); can also be a pyparsing expression
+ - ``content`` - expression for items within the nested lists
+ (default= ``None``)
+ - ``ignore_expr`` - expression for ignoring opening and closing delimiters
+ (default= :class:`quoted_string`)
+ - ``ignoreExpr`` - this pre-PEP8 argument is retained for compatibility
+ but will be removed in a future release
+
+ If an expression is not provided for the content argument, the
+ nested expression will capture all whitespace-delimited content
+ between delimiters as a list of separate values.
+
+ Use the ``ignore_expr`` argument to define expressions that may
+ contain opening or closing characters that should not be treated as
+ opening or closing characters for nesting, such as quoted_string or
+ a comment expression. Specify multiple expressions using an
+ :class:`Or` or :class:`MatchFirst`. The default is
+ :class:`quoted_string`, but if no expressions are to be ignored, then
+ pass ``None`` for this argument.
+
+ Example::
+
+ data_type = one_of("void int short long char float double")
+ decl_data_type = Combine(data_type + Opt(Word('*')))
+ ident = Word(alphas+'_', alphanums+'_')
+ number = pyparsing_common.number
+ arg = Group(decl_data_type + ident)
+ LPAR, RPAR = map(Suppress, "()")
+
+ code_body = nested_expr('{', '}', ignore_expr=(quoted_string | c_style_comment))
+
+ c_function = (decl_data_type("type")
+ + ident("name")
+ + LPAR + Opt(delimited_list(arg), [])("args") + RPAR
+ + code_body("body"))
+ c_function.ignore(c_style_comment)
+
+ source_code = '''
+ int is_odd(int x) {
+ return (x%2);
+ }
+
+ int dec_to_hex(char hchar) {
+ if (hchar >= '0' && hchar <= '9') {
+ return (ord(hchar)-ord('0'));
+ } else {
+ return (10+ord(hchar)-ord('A'));
+ }
+ }
+ '''
+ for func in c_function.search_string(source_code):
+ print("%(name)s (%(type)s) args: %(args)s" % func)
+
+
+ prints::
+
+ is_odd (int) args: [['int', 'x']]
+ dec_to_hex (int) args: [['char', 'hchar']]
+ """
+ if ignoreExpr != ignore_expr:
+ ignoreExpr = ignore_expr if ignoreExpr == quoted_string() else ignoreExpr
+ if opener == closer:
+ raise ValueError("opening and closing strings cannot be the same")
+ if content is None:
+ if isinstance(opener, str_type) and isinstance(closer, str_type):
+ if len(opener) == 1 and len(closer) == 1:
+ if ignoreExpr is not None:
+ content = Combine(
+ OneOrMore(
+ ~ignoreExpr
+ + CharsNotIn(
+ opener + closer + ParserElement.DEFAULT_WHITE_CHARS,
+ exact=1,
+ )
+ )
+ ).set_parse_action(lambda t: t[0].strip())
+ else:
+ content = empty.copy() + CharsNotIn(
+ opener + closer + ParserElement.DEFAULT_WHITE_CHARS
+ ).set_parse_action(lambda t: t[0].strip())
+ else:
+ if ignoreExpr is not None:
+ content = Combine(
+ OneOrMore(
+ ~ignoreExpr
+ + ~Literal(opener)
+ + ~Literal(closer)
+ + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1)
+ )
+ ).set_parse_action(lambda t: t[0].strip())
+ else:
+ content = Combine(
+ OneOrMore(
+ ~Literal(opener)
+ + ~Literal(closer)
+ + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1)
+ )
+ ).set_parse_action(lambda t: t[0].strip())
+ else:
+ raise ValueError(
+ "opening and closing arguments must be strings if no content expression is given"
+ )
+ ret = Forward()
+ if ignoreExpr is not None:
+ ret <<= Group(
+ Suppress(opener) + ZeroOrMore(ignoreExpr | ret | content) + Suppress(closer)
+ )
+ else:
+ ret <<= Group(Suppress(opener) + ZeroOrMore(ret | content) + Suppress(closer))
+ ret.set_name("nested %s%s expression" % (opener, closer))
+ return ret
+
+
+def _makeTags(tagStr, xml, suppress_LT=Suppress("<"), suppress_GT=Suppress(">")):
+ """Internal helper to construct opening and closing tag expressions, given a tag name"""
+ if isinstance(tagStr, str_type):
+ resname = tagStr
+ tagStr = Keyword(tagStr, caseless=not xml)
+ else:
+ resname = tagStr.name
+
+ tagAttrName = Word(alphas, alphanums + "_-:")
+ if xml:
+ tagAttrValue = dbl_quoted_string.copy().set_parse_action(remove_quotes)
+ openTag = (
+ suppress_LT
+ + tagStr("tag")
+ + Dict(ZeroOrMore(Group(tagAttrName + Suppress("=") + tagAttrValue)))
+ + Opt("/", default=[False])("empty").set_parse_action(
+ lambda s, l, t: t[0] == "/"
+ )
+ + suppress_GT
+ )
+ else:
+ tagAttrValue = quoted_string.copy().set_parse_action(remove_quotes) | Word(
+ printables, exclude_chars=">"
+ )
+ openTag = (
+ suppress_LT
+ + tagStr("tag")
+ + Dict(
+ ZeroOrMore(
+ Group(
+ tagAttrName.set_parse_action(lambda t: t[0].lower())
+ + Opt(Suppress("=") + tagAttrValue)
+ )
+ )
+ )
+ + Opt("/", default=[False])("empty").set_parse_action(
+ lambda s, l, t: t[0] == "/"
+ )
+ + suppress_GT
+ )
+ closeTag = Combine(Literal("</") + tagStr + ">", adjacent=False)
+
+ openTag.set_name("<%s>" % resname)
+ # add start<tagname> results name in parse action now that ungrouped names are not reported at two levels
+ openTag.add_parse_action(
+ lambda t: t.__setitem__(
+ "start" + "".join(resname.replace(":", " ").title().split()), t.copy()
+ )
+ )
+ closeTag = closeTag(
+ "end" + "".join(resname.replace(":", " ").title().split())
+ ).set_name("</%s>" % resname)
+ openTag.tag = resname
+ closeTag.tag = resname
+ openTag.tag_body = SkipTo(closeTag())
+ return openTag, closeTag
+
+
+def make_html_tags(
+ tag_str: Union[str, ParserElement]
+) -> Tuple[ParserElement, ParserElement]:
+ """Helper to construct opening and closing tag expressions for HTML,
+ given a tag name. Matches tags in either upper or lower case,
+ attributes with namespaces and with quoted or unquoted values.
+
+ Example::
+
+ text = '<td>More info at the <a href="https://github.com/pyparsing/pyparsing/wiki">pyparsing</a> wiki page</td>'
+ # make_html_tags returns pyparsing expressions for the opening and
+ # closing tags as a 2-tuple
+ a, a_end = make_html_tags("A")
+ link_expr = a + SkipTo(a_end)("link_text") + a_end
+
+ for link in link_expr.search_string(text):
+ # attributes in the <A> tag (like "href" shown here) are
+ # also accessible as named results
+ print(link.link_text, '->', link.href)
+
+ prints::
+
+ pyparsing -> https://github.com/pyparsing/pyparsing/wiki
+ """
+ return _makeTags(tag_str, False)
+
+
+def make_xml_tags(
+ tag_str: Union[str, ParserElement]
+) -> Tuple[ParserElement, ParserElement]:
+ """Helper to construct opening and closing tag expressions for XML,
+ given a tag name. Matches tags only in the given upper/lower case.
+
+ Example: similar to :class:`make_html_tags`
+ """
+ return _makeTags(tag_str, True)
+
+
+any_open_tag: ParserElement
+any_close_tag: ParserElement
+any_open_tag, any_close_tag = make_html_tags(
+ Word(alphas, alphanums + "_:").set_name("any tag")
+)
+
+_htmlEntityMap = {k.rstrip(";"): v for k, v in html.entities.html5.items()}
+common_html_entity = Regex("&(?P<entity>" + "|".join(_htmlEntityMap) + ");").set_name(
+ "common HTML entity"
+)
+
+
+def replace_html_entity(t):
+ """Helper parser action to replace common HTML entities with their special characters"""
+ return _htmlEntityMap.get(t.entity)
+
+
+class OpAssoc(Enum):
+ LEFT = 1
+ RIGHT = 2
+
+
+InfixNotationOperatorArgType = Union[
+ ParserElement, str, Tuple[Union[ParserElement, str], Union[ParserElement, str]]
+]
+InfixNotationOperatorSpec = Union[
+ Tuple[
+ InfixNotationOperatorArgType,
+ int,
+ OpAssoc,
+ typing.Optional[ParseAction],
+ ],
+ Tuple[
+ InfixNotationOperatorArgType,
+ int,
+ OpAssoc,
+ ],
+]
+
+
+def infix_notation(
+ base_expr: ParserElement,
+ op_list: List[InfixNotationOperatorSpec],
+ lpar: Union[str, ParserElement] = Suppress("("),
+ rpar: Union[str, ParserElement] = Suppress(")"),
+) -> ParserElement:
+ """Helper method for constructing grammars of expressions made up of
+ operators working in a precedence hierarchy. Operators may be unary
+ or binary, left- or right-associative. Parse actions can also be
+ attached to operator expressions. The generated parser will also
+ recognize the use of parentheses to override operator precedences
+ (see example below).
+
+ Note: if you define a deep operator list, you may see performance
+ issues when using infix_notation. See
+ :class:`ParserElement.enable_packrat` for a mechanism to potentially
+ improve your parser performance.
+
+ Parameters:
+ - ``base_expr`` - expression representing the most basic operand to
+ be used in the expression
+ - ``op_list`` - list of tuples, one for each operator precedence level
+ in the expression grammar; each tuple is of the form ``(op_expr,
+ num_operands, right_left_assoc, (optional)parse_action)``, where:
+
+ - ``op_expr`` is the pyparsing expression for the operator; may also
+ be a string, which will be converted to a Literal; if ``num_operands``
+ is 3, ``op_expr`` is a tuple of two expressions, for the two
+ operators separating the 3 terms
+ - ``num_operands`` is the number of terms for this operator (must be 1,
+ 2, or 3)
+ - ``right_left_assoc`` is the indicator whether the operator is right
+ or left associative, using the pyparsing-defined constants
+ ``OpAssoc.RIGHT`` and ``OpAssoc.LEFT``.
+ - ``parse_action`` is the parse action to be associated with
+ expressions matching this operator expression (the parse action
+ tuple member may be omitted); if the parse action is passed
+ a tuple or list of functions, this is equivalent to calling
+ ``set_parse_action(*fn)``
+ (:class:`ParserElement.set_parse_action`)
+ - ``lpar`` - expression for matching left-parentheses; if passed as a
+ str, then will be parsed as Suppress(lpar). If lpar is passed as
+ an expression (such as ``Literal('(')``), then it will be kept in
+ the parsed results, and grouped with them. (default= ``Suppress('(')``)
+ - ``rpar`` - expression for matching right-parentheses; if passed as a
+ str, then will be parsed as Suppress(rpar). If rpar is passed as
+ an expression (such as ``Literal(')')``), then it will be kept in
+ the parsed results, and grouped with them. (default= ``Suppress(')')``)
+
+ Example::
+
+ # simple example of four-function arithmetic with ints and
+ # variable names
+ integer = pyparsing_common.signed_integer
+ varname = pyparsing_common.identifier
+
+ arith_expr = infix_notation(integer | varname,
+ [
+ ('-', 1, OpAssoc.RIGHT),
+ (one_of('* /'), 2, OpAssoc.LEFT),
+ (one_of('+ -'), 2, OpAssoc.LEFT),
+ ])
+
+ arith_expr.run_tests('''
+ 5+3*6
+ (5+3)*6
+ -2--11
+ ''', full_dump=False)
+
+ prints::
+
+ 5+3*6
+ [[5, '+', [3, '*', 6]]]
+
+ (5+3)*6
+ [[[5, '+', 3], '*', 6]]
+
+ -2--11
+ [[['-', 2], '-', ['-', 11]]]
+ """
+ # captive version of FollowedBy that does not do parse actions or capture results names
+ class _FB(FollowedBy):
+ def parseImpl(self, instring, loc, doActions=True):
+ self.expr.try_parse(instring, loc)
+ return loc, []
+
+ _FB.__name__ = "FollowedBy>"
+
+ ret = Forward()
+ if isinstance(lpar, str):
+ lpar = Suppress(lpar)
+ if isinstance(rpar, str):
+ rpar = Suppress(rpar)
+
+ # if lpar and rpar are not suppressed, wrap in group
+ if not (isinstance(rpar, Suppress) and isinstance(rpar, Suppress)):
+ lastExpr = base_expr | Group(lpar + ret + rpar)
+ else:
+ lastExpr = base_expr | (lpar + ret + rpar)
+
+ for i, operDef in enumerate(op_list):
+ opExpr, arity, rightLeftAssoc, pa = (operDef + (None,))[:4]
+ if isinstance(opExpr, str_type):
+ opExpr = ParserElement._literalStringClass(opExpr)
+ if arity == 3:
+ if not isinstance(opExpr, (tuple, list)) or len(opExpr) != 2:
+ raise ValueError(
+ "if numterms=3, opExpr must be a tuple or list of two expressions"
+ )
+ opExpr1, opExpr2 = opExpr
+ term_name = "{}{} term".format(opExpr1, opExpr2)
+ else:
+ term_name = "{} term".format(opExpr)
+
+ if not 1 <= arity <= 3:
+ raise ValueError("operator must be unary (1), binary (2), or ternary (3)")
+
+ if rightLeftAssoc not in (OpAssoc.LEFT, OpAssoc.RIGHT):
+ raise ValueError("operator must indicate right or left associativity")
+
+ thisExpr: Forward = Forward().set_name(term_name)
+ if rightLeftAssoc is OpAssoc.LEFT:
+ if arity == 1:
+ matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + opExpr[1, ...])
+ elif arity == 2:
+ if opExpr is not None:
+ matchExpr = _FB(lastExpr + opExpr + lastExpr) + Group(
+ lastExpr + (opExpr + lastExpr)[1, ...]
+ )
+ else:
+ matchExpr = _FB(lastExpr + lastExpr) + Group(lastExpr[2, ...])
+ elif arity == 3:
+ matchExpr = _FB(
+ lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr
+ ) + Group(lastExpr + OneOrMore(opExpr1 + lastExpr + opExpr2 + lastExpr))
+ elif rightLeftAssoc is OpAssoc.RIGHT:
+ if arity == 1:
+ # try to avoid LR with this extra test
+ if not isinstance(opExpr, Opt):
+ opExpr = Opt(opExpr)
+ matchExpr = _FB(opExpr.expr + thisExpr) + Group(opExpr + thisExpr)
+ elif arity == 2:
+ if opExpr is not None:
+ matchExpr = _FB(lastExpr + opExpr + thisExpr) + Group(
+ lastExpr + (opExpr + thisExpr)[1, ...]
+ )
+ else:
+ matchExpr = _FB(lastExpr + thisExpr) + Group(
+ lastExpr + thisExpr[1, ...]
+ )
+ elif arity == 3:
+ matchExpr = _FB(
+ lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr
+ ) + Group(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr)
+ if pa:
+ if isinstance(pa, (tuple, list)):
+ matchExpr.set_parse_action(*pa)
+ else:
+ matchExpr.set_parse_action(pa)
+ thisExpr <<= (matchExpr | lastExpr).setName(term_name)
+ lastExpr = thisExpr
+ ret <<= lastExpr
+ return ret
+
+
+def indentedBlock(blockStatementExpr, indentStack, indent=True, backup_stacks=[]):
+ """
+ (DEPRECATED - use IndentedBlock class instead)
+ Helper method for defining space-delimited indentation blocks,
+ such as those used to define block statements in Python source code.
+
+ Parameters:
+
+ - ``blockStatementExpr`` - expression defining syntax of statement that
+ is repeated within the indented block
+ - ``indentStack`` - list created by caller to manage indentation stack
+ (multiple ``statementWithIndentedBlock`` expressions within a single
+ grammar should share a common ``indentStack``)
+ - ``indent`` - boolean indicating whether block must be indented beyond
+ the current level; set to ``False`` for block of left-most statements
+ (default= ``True``)
+
+ A valid block must contain at least one ``blockStatement``.
+
+ (Note that indentedBlock uses internal parse actions which make it
+ incompatible with packrat parsing.)
+
+ Example::
+
+ data = '''
+ def A(z):
+ A1
+ B = 100
+ G = A2
+ A2
+ A3
+ B
+ def BB(a,b,c):
+ BB1
+ def BBA():
+ bba1
+ bba2
+ bba3
+ C
+ D
+ def spam(x,y):
+ def eggs(z):
+ pass
+ '''
+
+
+ indentStack = [1]
+ stmt = Forward()
+
+ identifier = Word(alphas, alphanums)
+ funcDecl = ("def" + identifier + Group("(" + Opt(delimitedList(identifier)) + ")") + ":")
+ func_body = indentedBlock(stmt, indentStack)
+ funcDef = Group(funcDecl + func_body)
+
+ rvalue = Forward()
+ funcCall = Group(identifier + "(" + Opt(delimitedList(rvalue)) + ")")
+ rvalue << (funcCall | identifier | Word(nums))
+ assignment = Group(identifier + "=" + rvalue)
+ stmt << (funcDef | assignment | identifier)
+
+ module_body = stmt[1, ...]
+
+ parseTree = module_body.parseString(data)
+ parseTree.pprint()
+
+ prints::
+
+ [['def',
+ 'A',
+ ['(', 'z', ')'],
+ ':',
+ [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]],
+ 'B',
+ ['def',
+ 'BB',
+ ['(', 'a', 'b', 'c', ')'],
+ ':',
+ [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]],
+ 'C',
+ 'D',
+ ['def',
+ 'spam',
+ ['(', 'x', 'y', ')'],
+ ':',
+ [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]]
+ """
+ backup_stacks.append(indentStack[:])
+
+ def reset_stack():
+ indentStack[:] = backup_stacks[-1]
+
+ def checkPeerIndent(s, l, t):
+ if l >= len(s):
+ return
+ curCol = col(l, s)
+ if curCol != indentStack[-1]:
+ if curCol > indentStack[-1]:
+ raise ParseException(s, l, "illegal nesting")
+ raise ParseException(s, l, "not a peer entry")
+
+ def checkSubIndent(s, l, t):
+ curCol = col(l, s)
+ if curCol > indentStack[-1]:
+ indentStack.append(curCol)
+ else:
+ raise ParseException(s, l, "not a subentry")
+
+ def checkUnindent(s, l, t):
+ if l >= len(s):
+ return
+ curCol = col(l, s)
+ if not (indentStack and curCol in indentStack):
+ raise ParseException(s, l, "not an unindent")
+ if curCol < indentStack[-1]:
+ indentStack.pop()
+
+ NL = OneOrMore(LineEnd().set_whitespace_chars("\t ").suppress())
+ INDENT = (Empty() + Empty().set_parse_action(checkSubIndent)).set_name("INDENT")
+ PEER = Empty().set_parse_action(checkPeerIndent).set_name("")
+ UNDENT = Empty().set_parse_action(checkUnindent).set_name("UNINDENT")
+ if indent:
+ smExpr = Group(
+ Opt(NL)
+ + INDENT
+ + OneOrMore(PEER + Group(blockStatementExpr) + Opt(NL))
+ + UNDENT
+ )
+ else:
+ smExpr = Group(
+ Opt(NL)
+ + OneOrMore(PEER + Group(blockStatementExpr) + Opt(NL))
+ + Opt(UNDENT)
+ )
+
+ # add a parse action to remove backup_stack from list of backups
+ smExpr.add_parse_action(
+ lambda: backup_stacks.pop(-1) and None if backup_stacks else None
+ )
+ smExpr.set_fail_action(lambda a, b, c, d: reset_stack())
+ blockStatementExpr.ignore(_bslash + LineEnd())
+ return smExpr.set_name("indented block")
+
+
+# it's easy to get these comment structures wrong - they're very common, so may as well make them available
+c_style_comment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + "*/").set_name(
+ "C style comment"
+)
+"Comment of the form ``/* ... */``"
+
+html_comment = Regex(r"<!--[\s\S]*?-->").set_name("HTML comment")
+"Comment of the form ``<!-- ... -->``"
+
+rest_of_line = Regex(r".*").leave_whitespace().set_name("rest of line")
+dbl_slash_comment = Regex(r"//(?:\\\n|[^\n])*").set_name("// comment")
+"Comment of the form ``// ... (to end of line)``"
+
+cpp_style_comment = Combine(
+ Regex(r"/\*(?:[^*]|\*(?!/))*") + "*/" | dbl_slash_comment
+).set_name("C++ style comment")
+"Comment of either form :class:`c_style_comment` or :class:`dbl_slash_comment`"
+
+java_style_comment = cpp_style_comment
+"Same as :class:`cpp_style_comment`"
+
+python_style_comment = Regex(r"#.*").set_name("Python style comment")
+"Comment of the form ``# ... (to end of line)``"
+
+
+# build list of built-in expressions, for future reference if a global default value
+# gets updated
+_builtin_exprs: List[ParserElement] = [
+ v for v in vars().values() if isinstance(v, ParserElement)
+]
+
+
+# pre-PEP8 compatible names
+delimitedList = delimited_list
+countedArray = counted_array
+matchPreviousLiteral = match_previous_literal
+matchPreviousExpr = match_previous_expr
+oneOf = one_of
+dictOf = dict_of
+originalTextFor = original_text_for
+nestedExpr = nested_expr
+makeHTMLTags = make_html_tags
+makeXMLTags = make_xml_tags
+anyOpenTag, anyCloseTag = any_open_tag, any_close_tag
+commonHTMLEntity = common_html_entity
+replaceHTMLEntity = replace_html_entity
+opAssoc = OpAssoc
+infixNotation = infix_notation
+cStyleComment = c_style_comment
+htmlComment = html_comment
+restOfLine = rest_of_line
+dblSlashComment = dbl_slash_comment
+cppStyleComment = cpp_style_comment
+javaStyleComment = java_style_comment
+pythonStyleComment = python_style_comment
diff --git a/src/pip/_vendor/pyparsing/py.typed b/src/pip/_vendor/pyparsing/py.typed
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/pip/_vendor/pyparsing/py.typed
diff --git a/src/pip/_vendor/pyparsing/results.py b/src/pip/_vendor/pyparsing/results.py
new file mode 100644
index 000000000..00c9421d3
--- /dev/null
+++ b/src/pip/_vendor/pyparsing/results.py
@@ -0,0 +1,760 @@
+# results.py
+from collections.abc import MutableMapping, Mapping, MutableSequence, Iterator
+import pprint
+from weakref import ref as wkref
+from typing import Tuple, Any
+
+str_type: Tuple[type, ...] = (str, bytes)
+_generator_type = type((_ for _ in ()))
+
+
+class _ParseResultsWithOffset:
+ __slots__ = ["tup"]
+
+ def __init__(self, p1, p2):
+ self.tup = (p1, p2)
+
+ def __getitem__(self, i):
+ return self.tup[i]
+
+ def __getstate__(self):
+ return self.tup
+
+ def __setstate__(self, *args):
+ self.tup = args[0]
+
+
+class ParseResults:
+ """Structured parse results, to provide multiple means of access to
+ the parsed data:
+
+ - as a list (``len(results)``)
+ - by list index (``results[0], results[1]``, etc.)
+ - by attribute (``results.<results_name>`` - see :class:`ParserElement.set_results_name`)
+
+ Example::
+
+ integer = Word(nums)
+ date_str = (integer.set_results_name("year") + '/'
+ + integer.set_results_name("month") + '/'
+ + integer.set_results_name("day"))
+ # equivalent form:
+ # date_str = (integer("year") + '/'
+ # + integer("month") + '/'
+ # + integer("day"))
+
+ # parse_string returns a ParseResults object
+ result = date_str.parse_string("1999/12/31")
+
+ def test(s, fn=repr):
+ print("{} -> {}".format(s, fn(eval(s))))
+ test("list(result)")
+ test("result[0]")
+ test("result['month']")
+ test("result.day")
+ test("'month' in result")
+ test("'minutes' in result")
+ test("result.dump()", str)
+
+ prints::
+
+ list(result) -> ['1999', '/', '12', '/', '31']
+ result[0] -> '1999'
+ result['month'] -> '12'
+ result.day -> '31'
+ 'month' in result -> True
+ 'minutes' in result -> False
+ result.dump() -> ['1999', '/', '12', '/', '31']
+ - day: '31'
+ - month: '12'
+ - year: '1999'
+ """
+
+ _null_values: Tuple[Any, ...] = (None, [], "", ())
+
+ __slots__ = [
+ "_name",
+ "_parent",
+ "_all_names",
+ "_modal",
+ "_toklist",
+ "_tokdict",
+ "__weakref__",
+ ]
+
+ class List(list):
+ """
+ Simple wrapper class to distinguish parsed list results that should be preserved
+ as actual Python lists, instead of being converted to :class:`ParseResults`:
+
+ LBRACK, RBRACK = map(pp.Suppress, "[]")
+ element = pp.Forward()
+ item = ppc.integer
+ element_list = LBRACK + pp.delimited_list(element) + RBRACK
+
+ # add parse actions to convert from ParseResults to actual Python collection types
+ def as_python_list(t):
+ return pp.ParseResults.List(t.as_list())
+ element_list.add_parse_action(as_python_list)
+
+ element <<= item | element_list
+
+ element.run_tests('''
+ 100
+ [2,3,4]
+ [[2, 1],3,4]
+ [(2, 1),3,4]
+ (2,3,4)
+ ''', post_parse=lambda s, r: (r[0], type(r[0])))
+
+ prints:
+
+ 100
+ (100, <class 'int'>)
+
+ [2,3,4]
+ ([2, 3, 4], <class 'list'>)
+
+ [[2, 1],3,4]
+ ([[2, 1], 3, 4], <class 'list'>)
+
+ (Used internally by :class:`Group` when `aslist=True`.)
+ """
+
+ def __new__(cls, contained=None):
+ if contained is None:
+ contained = []
+
+ if not isinstance(contained, list):
+ raise TypeError(
+ "{} may only be constructed with a list,"
+ " not {}".format(cls.__name__, type(contained).__name__)
+ )
+
+ return list.__new__(cls)
+
+ def __new__(cls, toklist=None, name=None, **kwargs):
+ if isinstance(toklist, ParseResults):
+ return toklist
+ self = object.__new__(cls)
+ self._name = None
+ self._parent = None
+ self._all_names = set()
+
+ if toklist is None:
+ self._toklist = []
+ elif isinstance(toklist, (list, _generator_type)):
+ self._toklist = (
+ [toklist[:]]
+ if isinstance(toklist, ParseResults.List)
+ else list(toklist)
+ )
+ else:
+ self._toklist = [toklist]
+ self._tokdict = dict()
+ return self
+
+ # Performance tuning: we construct a *lot* of these, so keep this
+ # constructor as small and fast as possible
+ def __init__(
+ self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance
+ ):
+ self._modal = modal
+ if name is not None and name != "":
+ if isinstance(name, int):
+ name = str(name)
+ if not modal:
+ self._all_names = {name}
+ self._name = name
+ if toklist not in self._null_values:
+ if isinstance(toklist, (str_type, type)):
+ toklist = [toklist]
+ if asList:
+ if isinstance(toklist, ParseResults):
+ self[name] = _ParseResultsWithOffset(
+ ParseResults(toklist._toklist), 0
+ )
+ else:
+ self[name] = _ParseResultsWithOffset(
+ ParseResults(toklist[0]), 0
+ )
+ self[name]._name = name
+ else:
+ try:
+ self[name] = toklist[0]
+ except (KeyError, TypeError, IndexError):
+ if toklist is not self:
+ self[name] = toklist
+ else:
+ self._name = name
+
+ def __getitem__(self, i):
+ if isinstance(i, (int, slice)):
+ return self._toklist[i]
+ else:
+ if i not in self._all_names:
+ return self._tokdict[i][-1][0]
+ else:
+ return ParseResults([v[0] for v in self._tokdict[i]])
+
+ def __setitem__(self, k, v, isinstance=isinstance):
+ if isinstance(v, _ParseResultsWithOffset):
+ self._tokdict[k] = self._tokdict.get(k, list()) + [v]
+ sub = v[0]
+ elif isinstance(k, (int, slice)):
+ self._toklist[k] = v
+ sub = v
+ else:
+ self._tokdict[k] = self._tokdict.get(k, list()) + [
+ _ParseResultsWithOffset(v, 0)
+ ]
+ sub = v
+ if isinstance(sub, ParseResults):
+ sub._parent = wkref(self)
+
+ def __delitem__(self, i):
+ if isinstance(i, (int, slice)):
+ mylen = len(self._toklist)
+ del self._toklist[i]
+
+ # convert int to slice
+ if isinstance(i, int):
+ if i < 0:
+ i += mylen
+ i = slice(i, i + 1)
+ # get removed indices
+ removed = list(range(*i.indices(mylen)))
+ removed.reverse()
+ # fixup indices in token dictionary
+ for name, occurrences in self._tokdict.items():
+ for j in removed:
+ for k, (value, position) in enumerate(occurrences):
+ occurrences[k] = _ParseResultsWithOffset(
+ value, position - (position > j)
+ )
+ else:
+ del self._tokdict[i]
+
+ def __contains__(self, k) -> bool:
+ return k in self._tokdict
+
+ def __len__(self) -> int:
+ return len(self._toklist)
+
+ def __bool__(self) -> bool:
+ return not not (self._toklist or self._tokdict)
+
+ def __iter__(self) -> Iterator:
+ return iter(self._toklist)
+
+ def __reversed__(self) -> Iterator:
+ return iter(self._toklist[::-1])
+
+ def keys(self):
+ return iter(self._tokdict)
+
+ def values(self):
+ return (self[k] for k in self.keys())
+
+ def items(self):
+ return ((k, self[k]) for k in self.keys())
+
+ def haskeys(self) -> bool:
+ """
+ Since ``keys()`` returns an iterator, this method is helpful in bypassing
+ code that looks for the existence of any defined results names."""
+ return bool(self._tokdict)
+
+ def pop(self, *args, **kwargs):
+ """
+ Removes and returns item at specified index (default= ``last``).
+ Supports both ``list`` and ``dict`` semantics for ``pop()``. If
+ passed no argument or an integer argument, it will use ``list``
+ semantics and pop tokens from the list of parsed tokens. If passed
+ a non-integer argument (most likely a string), it will use ``dict``
+ semantics and pop the corresponding value from any defined results
+ names. A second default return value argument is supported, just as in
+ ``dict.pop()``.
+
+ Example::
+
+ numlist = Word(nums)[...]
+ print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321']
+
+ def remove_first(tokens):
+ tokens.pop(0)
+ numlist.add_parse_action(remove_first)
+ print(numlist.parse_string("0 123 321")) # -> ['123', '321']
+
+ label = Word(alphas)
+ patt = label("LABEL") + Word(nums)[1, ...]
+ print(patt.parse_string("AAB 123 321").dump())
+
+ # Use pop() in a parse action to remove named result (note that corresponding value is not
+ # removed from list form of results)
+ def remove_LABEL(tokens):
+ tokens.pop("LABEL")
+ return tokens
+ patt.add_parse_action(remove_LABEL)
+ print(patt.parse_string("AAB 123 321").dump())
+
+ prints::
+
+ ['AAB', '123', '321']
+ - LABEL: 'AAB'
+
+ ['AAB', '123', '321']
+ """
+ if not args:
+ args = [-1]
+ for k, v in kwargs.items():
+ if k == "default":
+ args = (args[0], v)
+ else:
+ raise TypeError(
+ "pop() got an unexpected keyword argument {!r}".format(k)
+ )
+ if isinstance(args[0], int) or len(args) == 1 or args[0] in self:
+ index = args[0]
+ ret = self[index]
+ del self[index]
+ return ret
+ else:
+ defaultvalue = args[1]
+ return defaultvalue
+
+ def get(self, key, default_value=None):
+ """
+ Returns named result matching the given key, or if there is no
+ such name, then returns the given ``default_value`` or ``None`` if no
+ ``default_value`` is specified.
+
+ Similar to ``dict.get()``.
+
+ Example::
+
+ integer = Word(nums)
+ date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+ result = date_str.parse_string("1999/12/31")
+ print(result.get("year")) # -> '1999'
+ print(result.get("hour", "not specified")) # -> 'not specified'
+ print(result.get("hour")) # -> None
+ """
+ if key in self:
+ return self[key]
+ else:
+ return default_value
+
+ def insert(self, index, ins_string):
+ """
+ Inserts new element at location index in the list of parsed tokens.
+
+ Similar to ``list.insert()``.
+
+ Example::
+
+ numlist = Word(nums)[...]
+ print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321']
+
+ # use a parse action to insert the parse location in the front of the parsed results
+ def insert_locn(locn, tokens):
+ tokens.insert(0, locn)
+ numlist.add_parse_action(insert_locn)
+ print(numlist.parse_string("0 123 321")) # -> [0, '0', '123', '321']
+ """
+ self._toklist.insert(index, ins_string)
+ # fixup indices in token dictionary
+ for name, occurrences in self._tokdict.items():
+ for k, (value, position) in enumerate(occurrences):
+ occurrences[k] = _ParseResultsWithOffset(
+ value, position + (position > index)
+ )
+
+ def append(self, item):
+ """
+ Add single element to end of ``ParseResults`` list of elements.
+
+ Example::
+
+ numlist = Word(nums)[...]
+ print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321']
+
+ # use a parse action to compute the sum of the parsed integers, and add it to the end
+ def append_sum(tokens):
+ tokens.append(sum(map(int, tokens)))
+ numlist.add_parse_action(append_sum)
+ print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321', 444]
+ """
+ self._toklist.append(item)
+
+ def extend(self, itemseq):
+ """
+ Add sequence of elements to end of ``ParseResults`` list of elements.
+
+ Example::
+
+ patt = Word(alphas)[1, ...]
+
+ # use a parse action to append the reverse of the matched strings, to make a palindrome
+ def make_palindrome(tokens):
+ tokens.extend(reversed([t[::-1] for t in tokens]))
+ return ''.join(tokens)
+ patt.add_parse_action(make_palindrome)
+ print(patt.parse_string("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl'
+ """
+ if isinstance(itemseq, ParseResults):
+ self.__iadd__(itemseq)
+ else:
+ self._toklist.extend(itemseq)
+
+ def clear(self):
+ """
+ Clear all elements and results names.
+ """
+ del self._toklist[:]
+ self._tokdict.clear()
+
+ def __getattr__(self, name):
+ try:
+ return self[name]
+ except KeyError:
+ if name.startswith("__"):
+ raise AttributeError(name)
+ return ""
+
+ def __add__(self, other) -> "ParseResults":
+ ret = self.copy()
+ ret += other
+ return ret
+
+ def __iadd__(self, other) -> "ParseResults":
+ if other._tokdict:
+ offset = len(self._toklist)
+ addoffset = lambda a: offset if a < 0 else a + offset
+ otheritems = other._tokdict.items()
+ otherdictitems = [
+ (k, _ParseResultsWithOffset(v[0], addoffset(v[1])))
+ for k, vlist in otheritems
+ for v in vlist
+ ]
+ for k, v in otherdictitems:
+ self[k] = v
+ if isinstance(v[0], ParseResults):
+ v[0]._parent = wkref(self)
+
+ self._toklist += other._toklist
+ self._all_names |= other._all_names
+ return self
+
+ def __radd__(self, other) -> "ParseResults":
+ if isinstance(other, int) and other == 0:
+ # useful for merging many ParseResults using sum() builtin
+ return self.copy()
+ else:
+ # this may raise a TypeError - so be it
+ return other + self
+
+ def __repr__(self) -> str:
+ return "{}({!r}, {})".format(type(self).__name__, self._toklist, self.as_dict())
+
+ def __str__(self) -> str:
+ return (
+ "["
+ + ", ".join(
+ [
+ str(i) if isinstance(i, ParseResults) else repr(i)
+ for i in self._toklist
+ ]
+ )
+ + "]"
+ )
+
+ def _asStringList(self, sep=""):
+ out = []
+ for item in self._toklist:
+ if out and sep:
+ out.append(sep)
+ if isinstance(item, ParseResults):
+ out += item._asStringList()
+ else:
+ out.append(str(item))
+ return out
+
+ def as_list(self) -> list:
+ """
+ Returns the parse results as a nested list of matching tokens, all converted to strings.
+
+ Example::
+
+ patt = Word(alphas)[1, ...]
+ result = patt.parse_string("sldkj lsdkj sldkj")
+ # even though the result prints in string-like form, it is actually a pyparsing ParseResults
+ print(type(result), result) # -> <class 'pyparsing.ParseResults'> ['sldkj', 'lsdkj', 'sldkj']
+
+ # Use as_list() to create an actual list
+ result_list = result.as_list()
+ print(type(result_list), result_list) # -> <class 'list'> ['sldkj', 'lsdkj', 'sldkj']
+ """
+ return [
+ res.as_list() if isinstance(res, ParseResults) else res
+ for res in self._toklist
+ ]
+
+ def as_dict(self) -> dict:
+ """
+ Returns the named parse results as a nested dictionary.
+
+ Example::
+
+ integer = Word(nums)
+ date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+ result = date_str.parse_string('12/31/1999')
+ print(type(result), repr(result)) # -> <class 'pyparsing.ParseResults'> (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]})
+
+ result_dict = result.as_dict()
+ print(type(result_dict), repr(result_dict)) # -> <class 'dict'> {'day': '1999', 'year': '12', 'month': '31'}
+
+ # even though a ParseResults supports dict-like access, sometime you just need to have a dict
+ import json
+ print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable
+ print(json.dumps(result.as_dict())) # -> {"month": "31", "day": "1999", "year": "12"}
+ """
+
+ def to_item(obj):
+ if isinstance(obj, ParseResults):
+ return obj.as_dict() if obj.haskeys() else [to_item(v) for v in obj]
+ else:
+ return obj
+
+ return dict((k, to_item(v)) for k, v in self.items())
+
+ def copy(self) -> "ParseResults":
+ """
+ Returns a new copy of a :class:`ParseResults` object.
+ """
+ ret = ParseResults(self._toklist)
+ ret._tokdict = self._tokdict.copy()
+ ret._parent = self._parent
+ ret._all_names |= self._all_names
+ ret._name = self._name
+ return ret
+
+ def get_name(self):
+ r"""
+ Returns the results name for this token expression. Useful when several
+ different expressions might match at a particular location.
+
+ Example::
+
+ integer = Word(nums)
+ ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d")
+ house_number_expr = Suppress('#') + Word(nums, alphanums)
+ user_data = (Group(house_number_expr)("house_number")
+ | Group(ssn_expr)("ssn")
+ | Group(integer)("age"))
+ user_info = user_data[1, ...]
+
+ result = user_info.parse_string("22 111-22-3333 #221B")
+ for item in result:
+ print(item.get_name(), ':', item[0])
+
+ prints::
+
+ age : 22
+ ssn : 111-22-3333
+ house_number : 221B
+ """
+ if self._name:
+ return self._name
+ elif self._parent:
+ par = self._parent()
+
+ def find_in_parent(sub):
+ return next(
+ (
+ k
+ for k, vlist in par._tokdict.items()
+ for v, loc in vlist
+ if sub is v
+ ),
+ None,
+ )
+
+ return find_in_parent(self) if par else None
+ elif (
+ len(self) == 1
+ and len(self._tokdict) == 1
+ and next(iter(self._tokdict.values()))[0][1] in (0, -1)
+ ):
+ return next(iter(self._tokdict.keys()))
+ else:
+ return None
+
+ def dump(self, indent="", full=True, include_list=True, _depth=0) -> str:
+ """
+ Diagnostic method for listing out the contents of
+ a :class:`ParseResults`. Accepts an optional ``indent`` argument so
+ that this string can be embedded in a nested display of other data.
+
+ Example::
+
+ integer = Word(nums)
+ date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+ result = date_str.parse_string('1999/12/31')
+ print(result.dump())
+
+ prints::
+
+ ['1999', '/', '12', '/', '31']
+ - day: '31'
+ - month: '12'
+ - year: '1999'
+ """
+ out = []
+ NL = "\n"
+ out.append(indent + str(self.as_list()) if include_list else "")
+
+ if full:
+ if self.haskeys():
+ items = sorted((str(k), v) for k, v in self.items())
+ for k, v in items:
+ if out:
+ out.append(NL)
+ out.append("{}{}- {}: ".format(indent, (" " * _depth), k))
+ if isinstance(v, ParseResults):
+ if v:
+ out.append(
+ v.dump(
+ indent=indent,
+ full=full,
+ include_list=include_list,
+ _depth=_depth + 1,
+ )
+ )
+ else:
+ out.append(str(v))
+ else:
+ out.append(repr(v))
+ if any(isinstance(vv, ParseResults) for vv in self):
+ v = self
+ for i, vv in enumerate(v):
+ if isinstance(vv, ParseResults):
+ out.append(
+ "\n{}{}[{}]:\n{}{}{}".format(
+ indent,
+ (" " * (_depth)),
+ i,
+ indent,
+ (" " * (_depth + 1)),
+ vv.dump(
+ indent=indent,
+ full=full,
+ include_list=include_list,
+ _depth=_depth + 1,
+ ),
+ )
+ )
+ else:
+ out.append(
+ "\n%s%s[%d]:\n%s%s%s"
+ % (
+ indent,
+ (" " * (_depth)),
+ i,
+ indent,
+ (" " * (_depth + 1)),
+ str(vv),
+ )
+ )
+
+ return "".join(out)
+
+ def pprint(self, *args, **kwargs):
+ """
+ Pretty-printer for parsed results as a list, using the
+ `pprint <https://docs.python.org/3/library/pprint.html>`_ module.
+ Accepts additional positional or keyword args as defined for
+ `pprint.pprint <https://docs.python.org/3/library/pprint.html#pprint.pprint>`_ .
+
+ Example::
+
+ ident = Word(alphas, alphanums)
+ num = Word(nums)
+ func = Forward()
+ term = ident | num | Group('(' + func + ')')
+ func <<= ident + Group(Optional(delimited_list(term)))
+ result = func.parse_string("fna a,b,(fnb c,d,200),100")
+ result.pprint(width=40)
+
+ prints::
+
+ ['fna',
+ ['a',
+ 'b',
+ ['(', 'fnb', ['c', 'd', '200'], ')'],
+ '100']]
+ """
+ pprint.pprint(self.as_list(), *args, **kwargs)
+
+ # add support for pickle protocol
+ def __getstate__(self):
+ return (
+ self._toklist,
+ (
+ self._tokdict.copy(),
+ self._parent is not None and self._parent() or None,
+ self._all_names,
+ self._name,
+ ),
+ )
+
+ def __setstate__(self, state):
+ self._toklist, (self._tokdict, par, inAccumNames, self._name) = state
+ self._all_names = set(inAccumNames)
+ if par is not None:
+ self._parent = wkref(par)
+ else:
+ self._parent = None
+
+ def __getnewargs__(self):
+ return self._toklist, self._name
+
+ def __dir__(self):
+ return dir(type(self)) + list(self.keys())
+
+ @classmethod
+ def from_dict(cls, other, name=None) -> "ParseResults":
+ """
+ Helper classmethod to construct a ``ParseResults`` from a ``dict``, preserving the
+ name-value relations as results names. If an optional ``name`` argument is
+ given, a nested ``ParseResults`` will be returned.
+ """
+
+ def is_iterable(obj):
+ try:
+ iter(obj)
+ except Exception:
+ return False
+ else:
+ return not isinstance(obj, str_type)
+
+ ret = cls([])
+ for k, v in other.items():
+ if isinstance(v, Mapping):
+ ret += cls.from_dict(v, name=k)
+ else:
+ ret += cls([v], name=k, asList=is_iterable(v))
+ if name is not None:
+ ret = cls([ret], name=name)
+ return ret
+
+ asList = as_list
+ asDict = as_dict
+ getName = get_name
+
+
+MutableMapping.register(ParseResults)
+MutableSequence.register(ParseResults)
diff --git a/src/pip/_vendor/pyparsing/testing.py b/src/pip/_vendor/pyparsing/testing.py
new file mode 100644
index 000000000..84a0ef170
--- /dev/null
+++ b/src/pip/_vendor/pyparsing/testing.py
@@ -0,0 +1,331 @@
+# testing.py
+
+from contextlib import contextmanager
+import typing
+
+from .core import (
+ ParserElement,
+ ParseException,
+ Keyword,
+ __diag__,
+ __compat__,
+)
+
+
+class pyparsing_test:
+ """
+ namespace class for classes useful in writing unit tests
+ """
+
+ class reset_pyparsing_context:
+ """
+ Context manager to be used when writing unit tests that modify pyparsing config values:
+ - packrat parsing
+ - bounded recursion parsing
+ - default whitespace characters.
+ - default keyword characters
+ - literal string auto-conversion class
+ - __diag__ settings
+
+ Example::
+
+ with reset_pyparsing_context():
+ # test that literals used to construct a grammar are automatically suppressed
+ ParserElement.inlineLiteralsUsing(Suppress)
+
+ term = Word(alphas) | Word(nums)
+ group = Group('(' + term[...] + ')')
+
+ # assert that the '()' characters are not included in the parsed tokens
+ self.assertParseAndCheckList(group, "(abc 123 def)", ['abc', '123', 'def'])
+
+ # after exiting context manager, literals are converted to Literal expressions again
+ """
+
+ def __init__(self):
+ self._save_context = {}
+
+ def save(self):
+ self._save_context["default_whitespace"] = ParserElement.DEFAULT_WHITE_CHARS
+ self._save_context["default_keyword_chars"] = Keyword.DEFAULT_KEYWORD_CHARS
+
+ self._save_context[
+ "literal_string_class"
+ ] = ParserElement._literalStringClass
+
+ self._save_context["verbose_stacktrace"] = ParserElement.verbose_stacktrace
+
+ self._save_context["packrat_enabled"] = ParserElement._packratEnabled
+ if ParserElement._packratEnabled:
+ self._save_context[
+ "packrat_cache_size"
+ ] = ParserElement.packrat_cache.size
+ else:
+ self._save_context["packrat_cache_size"] = None
+ self._save_context["packrat_parse"] = ParserElement._parse
+ self._save_context[
+ "recursion_enabled"
+ ] = ParserElement._left_recursion_enabled
+
+ self._save_context["__diag__"] = {
+ name: getattr(__diag__, name) for name in __diag__._all_names
+ }
+
+ self._save_context["__compat__"] = {
+ "collect_all_And_tokens": __compat__.collect_all_And_tokens
+ }
+
+ return self
+
+ def restore(self):
+ # reset pyparsing global state
+ if (
+ ParserElement.DEFAULT_WHITE_CHARS
+ != self._save_context["default_whitespace"]
+ ):
+ ParserElement.set_default_whitespace_chars(
+ self._save_context["default_whitespace"]
+ )
+
+ ParserElement.verbose_stacktrace = self._save_context["verbose_stacktrace"]
+
+ Keyword.DEFAULT_KEYWORD_CHARS = self._save_context["default_keyword_chars"]
+ ParserElement.inlineLiteralsUsing(
+ self._save_context["literal_string_class"]
+ )
+
+ for name, value in self._save_context["__diag__"].items():
+ (__diag__.enable if value else __diag__.disable)(name)
+
+ ParserElement._packratEnabled = False
+ if self._save_context["packrat_enabled"]:
+ ParserElement.enable_packrat(self._save_context["packrat_cache_size"])
+ else:
+ ParserElement._parse = self._save_context["packrat_parse"]
+ ParserElement._left_recursion_enabled = self._save_context[
+ "recursion_enabled"
+ ]
+
+ __compat__.collect_all_And_tokens = self._save_context["__compat__"]
+
+ return self
+
+ def copy(self):
+ ret = type(self)()
+ ret._save_context.update(self._save_context)
+ return ret
+
+ def __enter__(self):
+ return self.save()
+
+ def __exit__(self, *args):
+ self.restore()
+
+ class TestParseResultsAsserts:
+ """
+ A mixin class to add parse results assertion methods to normal unittest.TestCase classes.
+ """
+
+ def assertParseResultsEquals(
+ self, result, expected_list=None, expected_dict=None, msg=None
+ ):
+ """
+ Unit test assertion to compare a :class:`ParseResults` object with an optional ``expected_list``,
+ and compare any defined results names with an optional ``expected_dict``.
+ """
+ if expected_list is not None:
+ self.assertEqual(expected_list, result.as_list(), msg=msg)
+ if expected_dict is not None:
+ self.assertEqual(expected_dict, result.as_dict(), msg=msg)
+
+ def assertParseAndCheckList(
+ self, expr, test_string, expected_list, msg=None, verbose=True
+ ):
+ """
+ Convenience wrapper assert to test a parser element and input string, and assert that
+ the resulting ``ParseResults.asList()`` is equal to the ``expected_list``.
+ """
+ result = expr.parse_string(test_string, parse_all=True)
+ if verbose:
+ print(result.dump())
+ else:
+ print(result.as_list())
+ self.assertParseResultsEquals(result, expected_list=expected_list, msg=msg)
+
+ def assertParseAndCheckDict(
+ self, expr, test_string, expected_dict, msg=None, verbose=True
+ ):
+ """
+ Convenience wrapper assert to test a parser element and input string, and assert that
+ the resulting ``ParseResults.asDict()`` is equal to the ``expected_dict``.
+ """
+ result = expr.parse_string(test_string, parseAll=True)
+ if verbose:
+ print(result.dump())
+ else:
+ print(result.as_list())
+ self.assertParseResultsEquals(result, expected_dict=expected_dict, msg=msg)
+
+ def assertRunTestResults(
+ self, run_tests_report, expected_parse_results=None, msg=None
+ ):
+ """
+ Unit test assertion to evaluate output of ``ParserElement.runTests()``. If a list of
+ list-dict tuples is given as the ``expected_parse_results`` argument, then these are zipped
+ with the report tuples returned by ``runTests`` and evaluated using ``assertParseResultsEquals``.
+ Finally, asserts that the overall ``runTests()`` success value is ``True``.
+
+ :param run_tests_report: tuple(bool, [tuple(str, ParseResults or Exception)]) returned from runTests
+ :param expected_parse_results (optional): [tuple(str, list, dict, Exception)]
+ """
+ run_test_success, run_test_results = run_tests_report
+
+ if expected_parse_results is not None:
+ merged = [
+ (*rpt, expected)
+ for rpt, expected in zip(run_test_results, expected_parse_results)
+ ]
+ for test_string, result, expected in merged:
+ # expected should be a tuple containing a list and/or a dict or an exception,
+ # and optional failure message string
+ # an empty tuple will skip any result validation
+ fail_msg = next(
+ (exp for exp in expected if isinstance(exp, str)), None
+ )
+ expected_exception = next(
+ (
+ exp
+ for exp in expected
+ if isinstance(exp, type) and issubclass(exp, Exception)
+ ),
+ None,
+ )
+ if expected_exception is not None:
+ with self.assertRaises(
+ expected_exception=expected_exception, msg=fail_msg or msg
+ ):
+ if isinstance(result, Exception):
+ raise result
+ else:
+ expected_list = next(
+ (exp for exp in expected if isinstance(exp, list)), None
+ )
+ expected_dict = next(
+ (exp for exp in expected if isinstance(exp, dict)), None
+ )
+ if (expected_list, expected_dict) != (None, None):
+ self.assertParseResultsEquals(
+ result,
+ expected_list=expected_list,
+ expected_dict=expected_dict,
+ msg=fail_msg or msg,
+ )
+ else:
+ # warning here maybe?
+ print("no validation for {!r}".format(test_string))
+
+ # do this last, in case some specific test results can be reported instead
+ self.assertTrue(
+ run_test_success, msg=msg if msg is not None else "failed runTests"
+ )
+
+ @contextmanager
+ def assertRaisesParseException(self, exc_type=ParseException, msg=None):
+ with self.assertRaises(exc_type, msg=msg):
+ yield
+
+ @staticmethod
+ def with_line_numbers(
+ s: str,
+ start_line: typing.Optional[int] = None,
+ end_line: typing.Optional[int] = None,
+ expand_tabs: bool = True,
+ eol_mark: str = "|",
+ mark_spaces: typing.Optional[str] = None,
+ mark_control: typing.Optional[str] = None,
+ ) -> str:
+ """
+ Helpful method for debugging a parser - prints a string with line and column numbers.
+ (Line and column numbers are 1-based.)
+
+ :param s: tuple(bool, str - string to be printed with line and column numbers
+ :param start_line: int - (optional) starting line number in s to print (default=1)
+ :param end_line: int - (optional) ending line number in s to print (default=len(s))
+ :param expand_tabs: bool - (optional) expand tabs to spaces, to match the pyparsing default
+ :param eol_mark: str - (optional) string to mark the end of lines, helps visualize trailing spaces (default="|")
+ :param mark_spaces: str - (optional) special character to display in place of spaces
+ :param mark_control: str - (optional) convert non-printing control characters to a placeholding
+ character; valid values:
+ - "unicode" - replaces control chars with Unicode symbols, such as "␍" and "␊"
+ - any single character string - replace control characters with given string
+ - None (default) - string is displayed as-is
+
+ :return: str - input string with leading line numbers and column number headers
+ """
+ if expand_tabs:
+ s = s.expandtabs()
+ if mark_control is not None:
+ if mark_control == "unicode":
+ tbl = str.maketrans(
+ {c: u for c, u in zip(range(0, 33), range(0x2400, 0x2433))}
+ | {127: 0x2421}
+ )
+ eol_mark = ""
+ else:
+ tbl = str.maketrans(
+ {c: mark_control for c in list(range(0, 32)) + [127]}
+ )
+ s = s.translate(tbl)
+ if mark_spaces is not None and mark_spaces != " ":
+ if mark_spaces == "unicode":
+ tbl = str.maketrans({9: 0x2409, 32: 0x2423})
+ s = s.translate(tbl)
+ else:
+ s = s.replace(" ", mark_spaces)
+ if start_line is None:
+ start_line = 1
+ if end_line is None:
+ end_line = len(s)
+ end_line = min(end_line, len(s))
+ start_line = min(max(1, start_line), end_line)
+
+ if mark_control != "unicode":
+ s_lines = s.splitlines()[start_line - 1 : end_line]
+ else:
+ s_lines = [line + "␊" for line in s.split("␊")[start_line - 1 : end_line]]
+ if not s_lines:
+ return ""
+
+ lineno_width = len(str(end_line))
+ max_line_len = max(len(line) for line in s_lines)
+ lead = " " * (lineno_width + 1)
+ if max_line_len >= 99:
+ header0 = (
+ lead
+ + "".join(
+ "{}{}".format(" " * 99, (i + 1) % 100)
+ for i in range(max(max_line_len // 100, 1))
+ )
+ + "\n"
+ )
+ else:
+ header0 = ""
+ header1 = (
+ header0
+ + lead
+ + "".join(
+ " {}".format((i + 1) % 10)
+ for i in range(-(-max_line_len // 10))
+ )
+ + "\n"
+ )
+ header2 = lead + "1234567890" * (-(-max_line_len // 10)) + "\n"
+ return (
+ header1
+ + header2
+ + "\n".join(
+ "{:{}d}:{}{}".format(i, lineno_width, line, eol_mark)
+ for i, line in enumerate(s_lines, start=start_line)
+ )
+ + "\n"
+ )
diff --git a/src/pip/_vendor/pyparsing/unicode.py b/src/pip/_vendor/pyparsing/unicode.py
new file mode 100644
index 000000000..065262039
--- /dev/null
+++ b/src/pip/_vendor/pyparsing/unicode.py
@@ -0,0 +1,352 @@
+# unicode.py
+
+import sys
+from itertools import filterfalse
+from typing import List, Tuple, Union
+
+
+class _lazyclassproperty:
+ def __init__(self, fn):
+ self.fn = fn
+ self.__doc__ = fn.__doc__
+ self.__name__ = fn.__name__
+
+ def __get__(self, obj, cls):
+ if cls is None:
+ cls = type(obj)
+ if not hasattr(cls, "_intern") or any(
+ cls._intern is getattr(superclass, "_intern", [])
+ for superclass in cls.__mro__[1:]
+ ):
+ cls._intern = {}
+ attrname = self.fn.__name__
+ if attrname not in cls._intern:
+ cls._intern[attrname] = self.fn(cls)
+ return cls._intern[attrname]
+
+
+UnicodeRangeList = List[Union[Tuple[int, int], Tuple[int]]]
+
+
+class unicode_set:
+ """
+ A set of Unicode characters, for language-specific strings for
+ ``alphas``, ``nums``, ``alphanums``, and ``printables``.
+ A unicode_set is defined by a list of ranges in the Unicode character
+ set, in a class attribute ``_ranges``. Ranges can be specified using
+ 2-tuples or a 1-tuple, such as::
+
+ _ranges = [
+ (0x0020, 0x007e),
+ (0x00a0, 0x00ff),
+ (0x0100,),
+ ]
+
+ Ranges are left- and right-inclusive. A 1-tuple of (x,) is treated as (x, x).
+
+ A unicode set can also be defined using multiple inheritance of other unicode sets::
+
+ class CJK(Chinese, Japanese, Korean):
+ pass
+ """
+
+ _ranges: UnicodeRangeList = []
+
+ @_lazyclassproperty
+ def _chars_for_ranges(cls):
+ ret = []
+ for cc in cls.__mro__:
+ if cc is unicode_set:
+ break
+ for rr in getattr(cc, "_ranges", ()):
+ ret.extend(range(rr[0], rr[-1] + 1))
+ return [chr(c) for c in sorted(set(ret))]
+
+ @_lazyclassproperty
+ def printables(cls):
+ "all non-whitespace characters in this range"
+ return "".join(filterfalse(str.isspace, cls._chars_for_ranges))
+
+ @_lazyclassproperty
+ def alphas(cls):
+ "all alphabetic characters in this range"
+ return "".join(filter(str.isalpha, cls._chars_for_ranges))
+
+ @_lazyclassproperty
+ def nums(cls):
+ "all numeric digit characters in this range"
+ return "".join(filter(str.isdigit, cls._chars_for_ranges))
+
+ @_lazyclassproperty
+ def alphanums(cls):
+ "all alphanumeric characters in this range"
+ return cls.alphas + cls.nums
+
+ @_lazyclassproperty
+ def identchars(cls):
+ "all characters in this range that are valid identifier characters, plus underscore '_'"
+ return "".join(
+ sorted(
+ set(
+ "".join(filter(str.isidentifier, cls._chars_for_ranges))
+ + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzªµº"
+ + "ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ"
+ + "_"
+ )
+ )
+ )
+
+ @_lazyclassproperty
+ def identbodychars(cls):
+ """
+ all characters in this range that are valid identifier body characters,
+ plus the digits 0-9
+ """
+ return "".join(
+ sorted(
+ set(
+ cls.identchars
+ + "0123456789"
+ + "".join(
+ [c for c in cls._chars_for_ranges if ("_" + c).isidentifier()]
+ )
+ )
+ )
+ )
+
+
+class pyparsing_unicode(unicode_set):
+ """
+ A namespace class for defining common language unicode_sets.
+ """
+
+ # fmt: off
+
+ # define ranges in language character sets
+ _ranges: UnicodeRangeList = [
+ (0x0020, sys.maxunicode),
+ ]
+
+ class BasicMultilingualPlane(unicode_set):
+ "Unicode set for the Basic Multilingual Plane"
+ _ranges: UnicodeRangeList = [
+ (0x0020, 0xFFFF),
+ ]
+
+ class Latin1(unicode_set):
+ "Unicode set for Latin-1 Unicode Character Range"
+ _ranges: UnicodeRangeList = [
+ (0x0020, 0x007E),
+ (0x00A0, 0x00FF),
+ ]
+
+ class LatinA(unicode_set):
+ "Unicode set for Latin-A Unicode Character Range"
+ _ranges: UnicodeRangeList = [
+ (0x0100, 0x017F),
+ ]
+
+ class LatinB(unicode_set):
+ "Unicode set for Latin-B Unicode Character Range"
+ _ranges: UnicodeRangeList = [
+ (0x0180, 0x024F),
+ ]
+
+ class Greek(unicode_set):
+ "Unicode set for Greek Unicode Character Ranges"
+ _ranges: UnicodeRangeList = [
+ (0x0342, 0x0345),
+ (0x0370, 0x0377),
+ (0x037A, 0x037F),
+ (0x0384, 0x038A),
+ (0x038C,),
+ (0x038E, 0x03A1),
+ (0x03A3, 0x03E1),
+ (0x03F0, 0x03FF),
+ (0x1D26, 0x1D2A),
+ (0x1D5E,),
+ (0x1D60,),
+ (0x1D66, 0x1D6A),
+ (0x1F00, 0x1F15),
+ (0x1F18, 0x1F1D),
+ (0x1F20, 0x1F45),
+ (0x1F48, 0x1F4D),
+ (0x1F50, 0x1F57),
+ (0x1F59,),
+ (0x1F5B,),
+ (0x1F5D,),
+ (0x1F5F, 0x1F7D),
+ (0x1F80, 0x1FB4),
+ (0x1FB6, 0x1FC4),
+ (0x1FC6, 0x1FD3),
+ (0x1FD6, 0x1FDB),
+ (0x1FDD, 0x1FEF),
+ (0x1FF2, 0x1FF4),
+ (0x1FF6, 0x1FFE),
+ (0x2129,),
+ (0x2719, 0x271A),
+ (0xAB65,),
+ (0x10140, 0x1018D),
+ (0x101A0,),
+ (0x1D200, 0x1D245),
+ (0x1F7A1, 0x1F7A7),
+ ]
+
+ class Cyrillic(unicode_set):
+ "Unicode set for Cyrillic Unicode Character Range"
+ _ranges: UnicodeRangeList = [
+ (0x0400, 0x052F),
+ (0x1C80, 0x1C88),
+ (0x1D2B,),
+ (0x1D78,),
+ (0x2DE0, 0x2DFF),
+ (0xA640, 0xA672),
+ (0xA674, 0xA69F),
+ (0xFE2E, 0xFE2F),
+ ]
+
+ class Chinese(unicode_set):
+ "Unicode set for Chinese Unicode Character Range"
+ _ranges: UnicodeRangeList = [
+ (0x2E80, 0x2E99),
+ (0x2E9B, 0x2EF3),
+ (0x31C0, 0x31E3),
+ (0x3400, 0x4DB5),
+ (0x4E00, 0x9FEF),
+ (0xA700, 0xA707),
+ (0xF900, 0xFA6D),
+ (0xFA70, 0xFAD9),
+ (0x16FE2, 0x16FE3),
+ (0x1F210, 0x1F212),
+ (0x1F214, 0x1F23B),
+ (0x1F240, 0x1F248),
+ (0x20000, 0x2A6D6),
+ (0x2A700, 0x2B734),
+ (0x2B740, 0x2B81D),
+ (0x2B820, 0x2CEA1),
+ (0x2CEB0, 0x2EBE0),
+ (0x2F800, 0x2FA1D),
+ ]
+
+ class Japanese(unicode_set):
+ "Unicode set for Japanese Unicode Character Range, combining Kanji, Hiragana, and Katakana ranges"
+ _ranges: UnicodeRangeList = []
+
+ class Kanji(unicode_set):
+ "Unicode set for Kanji Unicode Character Range"
+ _ranges: UnicodeRangeList = [
+ (0x4E00, 0x9FBF),
+ (0x3000, 0x303F),
+ ]
+
+ class Hiragana(unicode_set):
+ "Unicode set for Hiragana Unicode Character Range"
+ _ranges: UnicodeRangeList = [
+ (0x3041, 0x3096),
+ (0x3099, 0x30A0),
+ (0x30FC,),
+ (0xFF70,),
+ (0x1B001,),
+ (0x1B150, 0x1B152),
+ (0x1F200,),
+ ]
+
+ class Katakana(unicode_set):
+ "Unicode set for Katakana Unicode Character Range"
+ _ranges: UnicodeRangeList = [
+ (0x3099, 0x309C),
+ (0x30A0, 0x30FF),
+ (0x31F0, 0x31FF),
+ (0x32D0, 0x32FE),
+ (0xFF65, 0xFF9F),
+ (0x1B000,),
+ (0x1B164, 0x1B167),
+ (0x1F201, 0x1F202),
+ (0x1F213,),
+ ]
+
+ class Hangul(unicode_set):
+ "Unicode set for Hangul (Korean) Unicode Character Range"
+ _ranges: UnicodeRangeList = [
+ (0x1100, 0x11FF),
+ (0x302E, 0x302F),
+ (0x3131, 0x318E),
+ (0x3200, 0x321C),
+ (0x3260, 0x327B),
+ (0x327E,),
+ (0xA960, 0xA97C),
+ (0xAC00, 0xD7A3),
+ (0xD7B0, 0xD7C6),
+ (0xD7CB, 0xD7FB),
+ (0xFFA0, 0xFFBE),
+ (0xFFC2, 0xFFC7),
+ (0xFFCA, 0xFFCF),
+ (0xFFD2, 0xFFD7),
+ (0xFFDA, 0xFFDC),
+ ]
+
+ Korean = Hangul
+
+ class CJK(Chinese, Japanese, Hangul):
+ "Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range"
+
+ class Thai(unicode_set):
+ "Unicode set for Thai Unicode Character Range"
+ _ranges: UnicodeRangeList = [
+ (0x0E01, 0x0E3A),
+ (0x0E3F, 0x0E5B)
+ ]
+
+ class Arabic(unicode_set):
+ "Unicode set for Arabic Unicode Character Range"
+ _ranges: UnicodeRangeList = [
+ (0x0600, 0x061B),
+ (0x061E, 0x06FF),
+ (0x0700, 0x077F),
+ ]
+
+ class Hebrew(unicode_set):
+ "Unicode set for Hebrew Unicode Character Range"
+ _ranges: UnicodeRangeList = [
+ (0x0591, 0x05C7),
+ (0x05D0, 0x05EA),
+ (0x05EF, 0x05F4),
+ (0xFB1D, 0xFB36),
+ (0xFB38, 0xFB3C),
+ (0xFB3E,),
+ (0xFB40, 0xFB41),
+ (0xFB43, 0xFB44),
+ (0xFB46, 0xFB4F),
+ ]
+
+ class Devanagari(unicode_set):
+ "Unicode set for Devanagari Unicode Character Range"
+ _ranges: UnicodeRangeList = [
+ (0x0900, 0x097F),
+ (0xA8E0, 0xA8FF)
+ ]
+
+ # fmt: on
+
+
+pyparsing_unicode.Japanese._ranges = (
+ pyparsing_unicode.Japanese.Kanji._ranges
+ + pyparsing_unicode.Japanese.Hiragana._ranges
+ + pyparsing_unicode.Japanese.Katakana._ranges
+)
+
+pyparsing_unicode.BMP = pyparsing_unicode.BasicMultilingualPlane
+
+# add language identifiers using language Unicode
+pyparsing_unicode.العربية = pyparsing_unicode.Arabic
+pyparsing_unicode.中文 = pyparsing_unicode.Chinese
+pyparsing_unicode.кириллица = pyparsing_unicode.Cyrillic
+pyparsing_unicode.Ελληνικά = pyparsing_unicode.Greek
+pyparsing_unicode.עִברִית = pyparsing_unicode.Hebrew
+pyparsing_unicode.日本語 = pyparsing_unicode.Japanese
+pyparsing_unicode.Japanese.漢字 = pyparsing_unicode.Japanese.Kanji
+pyparsing_unicode.Japanese.カタカナ = pyparsing_unicode.Japanese.Katakana
+pyparsing_unicode.Japanese.ひらがな = pyparsing_unicode.Japanese.Hiragana
+pyparsing_unicode.한국어 = pyparsing_unicode.Korean
+pyparsing_unicode.ไทย = pyparsing_unicode.Thai
+pyparsing_unicode.देवनागरी = pyparsing_unicode.Devanagari
diff --git a/src/pip/_vendor/pyparsing/util.py b/src/pip/_vendor/pyparsing/util.py
new file mode 100644
index 000000000..34ce092c6
--- /dev/null
+++ b/src/pip/_vendor/pyparsing/util.py
@@ -0,0 +1,235 @@
+# util.py
+import warnings
+import types
+import collections
+import itertools
+from functools import lru_cache
+from typing import List, Union, Iterable
+
+_bslash = chr(92)
+
+
+class __config_flags:
+ """Internal class for defining compatibility and debugging flags"""
+
+ _all_names: List[str] = []
+ _fixed_names: List[str] = []
+ _type_desc = "configuration"
+
+ @classmethod
+ def _set(cls, dname, value):
+ if dname in cls._fixed_names:
+ warnings.warn(
+ "{}.{} {} is {} and cannot be overridden".format(
+ cls.__name__,
+ dname,
+ cls._type_desc,
+ str(getattr(cls, dname)).upper(),
+ )
+ )
+ return
+ if dname in cls._all_names:
+ setattr(cls, dname, value)
+ else:
+ raise ValueError("no such {} {!r}".format(cls._type_desc, dname))
+
+ enable = classmethod(lambda cls, name: cls._set(name, True))
+ disable = classmethod(lambda cls, name: cls._set(name, False))
+
+
+@lru_cache(maxsize=128)
+def col(loc: int, strg: str) -> int:
+ """
+ Returns current column within a string, counting newlines as line separators.
+ The first column is number 1.
+
+ Note: the default parsing behavior is to expand tabs in the input string
+ before starting the parsing process. See
+ :class:`ParserElement.parseString` for more
+ information on parsing strings containing ``<TAB>`` s, and suggested
+ methods to maintain a consistent view of the parsed string, the parse
+ location, and line and column positions within the parsed string.
+ """
+ s = strg
+ return 1 if 0 < loc < len(s) and s[loc - 1] == "\n" else loc - s.rfind("\n", 0, loc)
+
+
+@lru_cache(maxsize=128)
+def lineno(loc: int, strg: str) -> int:
+ """Returns current line number within a string, counting newlines as line separators.
+ The first line is number 1.
+
+ Note - the default parsing behavior is to expand tabs in the input string
+ before starting the parsing process. See :class:`ParserElement.parseString`
+ for more information on parsing strings containing ``<TAB>`` s, and
+ suggested methods to maintain a consistent view of the parsed string, the
+ parse location, and line and column positions within the parsed string.
+ """
+ return strg.count("\n", 0, loc) + 1
+
+
+@lru_cache(maxsize=128)
+def line(loc: int, strg: str) -> str:
+ """
+ Returns the line of text containing loc within a string, counting newlines as line separators.
+ """
+ last_cr = strg.rfind("\n", 0, loc)
+ next_cr = strg.find("\n", loc)
+ return strg[last_cr + 1 : next_cr] if next_cr >= 0 else strg[last_cr + 1 :]
+
+
+class _UnboundedCache:
+ def __init__(self):
+ cache = {}
+ cache_get = cache.get
+ self.not_in_cache = not_in_cache = object()
+
+ def get(_, key):
+ return cache_get(key, not_in_cache)
+
+ def set_(_, key, value):
+ cache[key] = value
+
+ def clear(_):
+ cache.clear()
+
+ self.size = None
+ self.get = types.MethodType(get, self)
+ self.set = types.MethodType(set_, self)
+ self.clear = types.MethodType(clear, self)
+
+
+class _FifoCache:
+ def __init__(self, size):
+ self.not_in_cache = not_in_cache = object()
+ cache = collections.OrderedDict()
+ cache_get = cache.get
+
+ def get(_, key):
+ return cache_get(key, not_in_cache)
+
+ def set_(_, key, value):
+ cache[key] = value
+ while len(cache) > size:
+ cache.popitem(last=False)
+
+ def clear(_):
+ cache.clear()
+
+ self.size = size
+ self.get = types.MethodType(get, self)
+ self.set = types.MethodType(set_, self)
+ self.clear = types.MethodType(clear, self)
+
+
+class LRUMemo:
+ """
+ A memoizing mapping that retains `capacity` deleted items
+
+ The memo tracks retained items by their access order; once `capacity` items
+ are retained, the least recently used item is discarded.
+ """
+
+ def __init__(self, capacity):
+ self._capacity = capacity
+ self._active = {}
+ self._memory = collections.OrderedDict()
+
+ def __getitem__(self, key):
+ try:
+ return self._active[key]
+ except KeyError:
+ self._memory.move_to_end(key)
+ return self._memory[key]
+
+ def __setitem__(self, key, value):
+ self._memory.pop(key, None)
+ self._active[key] = value
+
+ def __delitem__(self, key):
+ try:
+ value = self._active.pop(key)
+ except KeyError:
+ pass
+ else:
+ while len(self._memory) >= self._capacity:
+ self._memory.popitem(last=False)
+ self._memory[key] = value
+
+ def clear(self):
+ self._active.clear()
+ self._memory.clear()
+
+
+class UnboundedMemo(dict):
+ """
+ A memoizing mapping that retains all deleted items
+ """
+
+ def __delitem__(self, key):
+ pass
+
+
+def _escape_regex_range_chars(s: str) -> str:
+ # escape these chars: ^-[]
+ for c in r"\^-[]":
+ s = s.replace(c, _bslash + c)
+ s = s.replace("\n", r"\n")
+ s = s.replace("\t", r"\t")
+ return str(s)
+
+
+def _collapse_string_to_ranges(
+ s: Union[str, Iterable[str]], re_escape: bool = True
+) -> str:
+ def is_consecutive(c):
+ c_int = ord(c)
+ is_consecutive.prev, prev = c_int, is_consecutive.prev
+ if c_int - prev > 1:
+ is_consecutive.value = next(is_consecutive.counter)
+ return is_consecutive.value
+
+ is_consecutive.prev = 0
+ is_consecutive.counter = itertools.count()
+ is_consecutive.value = -1
+
+ def escape_re_range_char(c):
+ return "\\" + c if c in r"\^-][" else c
+
+ def no_escape_re_range_char(c):
+ return c
+
+ if not re_escape:
+ escape_re_range_char = no_escape_re_range_char
+
+ ret = []
+ s = "".join(sorted(set(s)))
+ if len(s) > 3:
+ for _, chars in itertools.groupby(s, key=is_consecutive):
+ first = last = next(chars)
+ last = collections.deque(
+ itertools.chain(iter([last]), chars), maxlen=1
+ ).pop()
+ if first == last:
+ ret.append(escape_re_range_char(first))
+ else:
+ sep = "" if ord(last) == ord(first) + 1 else "-"
+ ret.append(
+ "{}{}{}".format(
+ escape_re_range_char(first), sep, escape_re_range_char(last)
+ )
+ )
+ else:
+ ret = [escape_re_range_char(c) for c in s]
+
+ return "".join(ret)
+
+
+def _flatten(ll: list) -> list:
+ ret = []
+ for i in ll:
+ if isinstance(i, list):
+ ret.extend(_flatten(i))
+ else:
+ ret.append(i)
+ return ret
diff --git a/src/pip/_vendor/pyproject_hooks.pyi b/src/pip/_vendor/pyproject_hooks.pyi
new file mode 100644
index 000000000..e68245481
--- /dev/null
+++ b/src/pip/_vendor/pyproject_hooks.pyi
@@ -0,0 +1 @@
+from pyproject_hooks import * \ No newline at end of file
diff --git a/src/pip/_vendor/pep517/LICENSE b/src/pip/_vendor/pyproject_hooks/LICENSE
index b0ae9dbc2..b0ae9dbc2 100644
--- a/src/pip/_vendor/pep517/LICENSE
+++ b/src/pip/_vendor/pyproject_hooks/LICENSE
diff --git a/src/pip/_vendor/pyproject_hooks/__init__.py b/src/pip/_vendor/pyproject_hooks/__init__.py
new file mode 100644
index 000000000..ddfcf7f72
--- /dev/null
+++ b/src/pip/_vendor/pyproject_hooks/__init__.py
@@ -0,0 +1,23 @@
+"""Wrappers to call pyproject.toml-based build backend hooks.
+"""
+
+from ._impl import (
+ BackendInvalid,
+ BackendUnavailable,
+ BuildBackendHookCaller,
+ HookMissing,
+ UnsupportedOperation,
+ default_subprocess_runner,
+ quiet_subprocess_runner,
+)
+
+__version__ = '1.0.0'
+__all__ = [
+ 'BackendUnavailable',
+ 'BackendInvalid',
+ 'HookMissing',
+ 'UnsupportedOperation',
+ 'default_subprocess_runner',
+ 'quiet_subprocess_runner',
+ 'BuildBackendHookCaller',
+]
diff --git a/src/pip/_vendor/pyproject_hooks/_compat.py b/src/pip/_vendor/pyproject_hooks/_compat.py
new file mode 100644
index 000000000..95e509c01
--- /dev/null
+++ b/src/pip/_vendor/pyproject_hooks/_compat.py
@@ -0,0 +1,8 @@
+__all__ = ("tomllib",)
+
+import sys
+
+if sys.version_info >= (3, 11):
+ import tomllib
+else:
+ from pip._vendor import tomli as tomllib
diff --git a/src/pip/_vendor/pep517/wrappers.py b/src/pip/_vendor/pyproject_hooks/_impl.py
index e031ed708..37b0e6531 100644
--- a/src/pip/_vendor/pep517/wrappers.py
+++ b/src/pip/_vendor/pyproject_hooks/_impl.py
@@ -1,33 +1,23 @@
-import threading
-from contextlib import contextmanager
+import json
import os
-from os.path import abspath, join as pjoin
-import shutil
-from subprocess import check_call, check_output, STDOUT
import sys
-from tempfile import mkdtemp
+import tempfile
+from contextlib import contextmanager
+from os.path import abspath
+from os.path import join as pjoin
+from subprocess import STDOUT, check_call, check_output
+
+from ._in_process import _in_proc_script_path
-from . import compat
-from .in_process import _in_proc_script_path
-__all__ = [
- 'BackendUnavailable',
- 'BackendInvalid',
- 'HookMissing',
- 'UnsupportedOperation',
- 'default_subprocess_runner',
- 'quiet_subprocess_runner',
- 'Pep517HookCaller',
-]
+def write_json(obj, path, **kwargs):
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(obj, f, **kwargs)
-@contextmanager
-def tempdir():
- td = mkdtemp()
- try:
- yield td
- finally:
- shutil.rmtree(td)
+def read_json(path):
+ with open(path, encoding='utf-8') as f:
+ return json.load(f)
class BackendUnavailable(Exception):
@@ -39,15 +29,15 @@ class BackendUnavailable(Exception):
class BackendInvalid(Exception):
"""Will be raised if the backend is invalid."""
def __init__(self, backend_name, backend_path, message):
+ super().__init__(message)
self.backend_name = backend_name
self.backend_path = backend_path
- self.message = message
class HookMissing(Exception):
- """Will be raised on missing hooks."""
+ """Will be raised on missing hooks (if a fallback can't be used)."""
def __init__(self, hook_name):
- super(HookMissing, self).__init__(hook_name)
+ super().__init__(hook_name)
self.hook_name = hook_name
@@ -58,7 +48,10 @@ class UnsupportedOperation(Exception):
def default_subprocess_runner(cmd, cwd=None, extra_environ=None):
- """The default method of calling the wrapper subprocess."""
+ """The default method of calling the wrapper subprocess.
+
+ This uses :func:`subprocess.check_call` under the hood.
+ """
env = os.environ.copy()
if extra_environ:
env.update(extra_environ)
@@ -67,7 +60,10 @@ def default_subprocess_runner(cmd, cwd=None, extra_environ=None):
def quiet_subprocess_runner(cmd, cwd=None, extra_environ=None):
- """A method of calling the wrapper subprocess while suppressing output."""
+ """Call the subprocess while suppressing output.
+
+ This uses :func:`subprocess.check_output` under the hood.
+ """
env = os.environ.copy()
if extra_environ:
env.update(extra_environ)
@@ -99,26 +95,10 @@ def norm_and_check(source_tree, requested):
return abs_requested
-class Pep517HookCaller(object):
- """A wrapper around a source directory to be built with a PEP 517 backend.
-
- :param source_dir: The path to the source directory, containing
- pyproject.toml.
- :param build_backend: The build backend spec, as per PEP 517, from
- pyproject.toml.
- :param backend_path: The backend path, as per PEP 517, from pyproject.toml.
- :param runner: A callable that invokes the wrapper subprocess.
- :param python_executable: The Python executable used to invoke the backend
-
- The 'runner', if provided, must expect the following:
-
- - cmd: a list of strings representing the command and arguments to
- execute, as would be passed to e.g. 'subprocess.check_call'.
- - cwd: a string representing the working directory that must be
- used for the subprocess. Corresponds to the provided source_dir.
- - extra_environ: a dict mapping environment variable names to values
- which must be set for the subprocess execution.
+class BuildBackendHookCaller:
+ """A wrapper to call the build backend hooks for a source directory.
"""
+
def __init__(
self,
source_dir,
@@ -127,6 +107,14 @@ class Pep517HookCaller(object):
runner=None,
python_executable=None,
):
+ """
+ :param source_dir: The source directory to invoke the build backend for
+ :param build_backend: The build backend spec
+ :param backend_path: Additional path entries for the build backend spec
+ :param runner: The :ref:`subprocess runner <Subprocess Runners>` to use
+ :param python_executable:
+ The Python executable used to invoke the build backend
+ """
if runner is None:
runner = default_subprocess_runner
@@ -144,8 +132,14 @@ class Pep517HookCaller(object):
@contextmanager
def subprocess_runner(self, runner):
- """A context manager for temporarily overriding the default subprocess
- runner.
+ """A context manager for temporarily overriding the default
+ :ref:`subprocess runner <Subprocess Runners>`.
+
+ .. code-block:: python
+
+ hook_caller = BuildBackendHookCaller(...)
+ with hook_caller.subprocess_runner(quiet_subprocess_runner):
+ ...
"""
prev = self._subprocess_runner
self._subprocess_runner = runner
@@ -159,15 +153,15 @@ class Pep517HookCaller(object):
return self._call_hook('_supported_features', {})
def get_requires_for_build_wheel(self, config_settings=None):
- """Identify packages required for building a wheel
+ """Get additional dependencies required for building a wheel.
- Returns a list of dependency specifications, e.g.::
+ :returns: A list of :pep:`dependency specifiers <508>`.
+ :rtype: list[str]
- ["wheel >= 0.25", "setuptools"]
+ .. admonition:: Fallback
- This does not include requirements specified in pyproject.toml.
- It returns the result of calling the equivalently named hook in a
- subprocess.
+ If the build backend does not defined a hook with this name, an
+ empty list will be returned.
"""
return self._call_hook('get_requires_for_build_wheel', {
'config_settings': config_settings
@@ -178,12 +172,16 @@ class Pep517HookCaller(object):
_allow_fallback=True):
"""Prepare a ``*.dist-info`` folder with metadata for this project.
- Returns the name of the newly created folder.
+ :returns: Name of the newly created subfolder within
+ ``metadata_directory``, containing the metadata.
+ :rtype: str
- If the build backend defines a hook with this name, it will be called
- in a subprocess. If not, the backend will be asked to build a wheel,
- and the dist-info extracted from that (unless _allow_fallback is
- False).
+ .. admonition:: Fallback
+
+ If the build backend does not define a hook with this name and
+ ``_allow_fallback`` is truthy, the backend will be asked to build a
+ wheel via the ``build_wheel`` hook and the dist-info extracted from
+ that will be returned.
"""
return self._call_hook('prepare_metadata_for_build_wheel', {
'metadata_directory': abspath(metadata_directory),
@@ -196,12 +194,15 @@ class Pep517HookCaller(object):
metadata_directory=None):
"""Build a wheel from this project.
- Returns the name of the newly created file.
+ :returns:
+ The name of the newly created wheel within ``wheel_directory``.
+
+ .. admonition:: Interaction with fallback
- In general, this will call the 'build_wheel' hook in the backend.
- However, if that was previously called by
- 'prepare_metadata_for_build_wheel', and the same metadata_directory is
- used, the previously built wheel will be copied to wheel_directory.
+ If the ``build_wheel`` hook was called in the fallback for
+ :meth:`prepare_metadata_for_build_wheel`, the build backend would
+ not be invoked. Instead, the previously built wheel will be copied
+ to ``wheel_directory`` and the name of that file will be returned.
"""
if metadata_directory is not None:
metadata_directory = abspath(metadata_directory)
@@ -212,15 +213,15 @@ class Pep517HookCaller(object):
})
def get_requires_for_build_editable(self, config_settings=None):
- """Identify packages required for building an editable wheel
+ """Get additional dependencies required for building an editable wheel.
- Returns a list of dependency specifications, e.g.::
+ :returns: A list of :pep:`dependency specifiers <508>`.
+ :rtype: list[str]
- ["wheel >= 0.25", "setuptools"]
+ .. admonition:: Fallback
- This does not include requirements specified in pyproject.toml.
- It returns the result of calling the equivalently named hook in a
- subprocess.
+ If the build backend does not defined a hook with this name, an
+ empty list will be returned.
"""
return self._call_hook('get_requires_for_build_editable', {
'config_settings': config_settings
@@ -231,12 +232,16 @@ class Pep517HookCaller(object):
_allow_fallback=True):
"""Prepare a ``*.dist-info`` folder with metadata for this project.
- Returns the name of the newly created folder.
+ :returns: Name of the newly created subfolder within
+ ``metadata_directory``, containing the metadata.
+ :rtype: str
- If the build backend defines a hook with this name, it will be called
- in a subprocess. If not, the backend will be asked to build an editable
- wheel, and the dist-info extracted from that (unless _allow_fallback is
- False).
+ .. admonition:: Fallback
+
+ If the build backend does not define a hook with this name and
+ ``_allow_fallback`` is truthy, the backend will be asked to build a
+ wheel via the ``build_editable`` hook and the dist-info
+ extracted from that will be returned.
"""
return self._call_hook('prepare_metadata_for_build_editable', {
'metadata_directory': abspath(metadata_directory),
@@ -249,12 +254,16 @@ class Pep517HookCaller(object):
metadata_directory=None):
"""Build an editable wheel from this project.
- Returns the name of the newly created file.
+ :returns:
+ The name of the newly created wheel within ``wheel_directory``.
+
+ .. admonition:: Interaction with fallback
- In general, this will call the 'build_editable' hook in the backend.
- However, if that was previously called by
- 'prepare_metadata_for_build_editable', and the same metadata_directory
- is used, the previously built wheel will be copied to wheel_directory.
+ If the ``build_editable`` hook was called in the fallback for
+ :meth:`prepare_metadata_for_build_editable`, the build backend
+ would not be invoked. Instead, the previously built wheel will be
+ copied to ``wheel_directory`` and the name of that file will be
+ returned.
"""
if metadata_directory is not None:
metadata_directory = abspath(metadata_directory)
@@ -265,15 +274,10 @@ class Pep517HookCaller(object):
})
def get_requires_for_build_sdist(self, config_settings=None):
- """Identify packages required for building a wheel
-
- Returns a list of dependency specifications, e.g.::
+ """Get additional dependencies required for building an sdist.
- ["setuptools >= 26"]
-
- This does not include requirements specified in pyproject.toml.
- It returns the result of calling the equivalently named hook in a
- subprocess.
+ :returns: A list of :pep:`dependency specifiers <508>`.
+ :rtype: list[str]
"""
return self._call_hook('get_requires_for_build_sdist', {
'config_settings': config_settings
@@ -282,9 +286,8 @@ class Pep517HookCaller(object):
def build_sdist(self, sdist_directory, config_settings=None):
"""Build an sdist from this project.
- Returns the name of the newly created file.
-
- This calls the 'build_sdist' backend hook in a subprocess.
+ :returns:
+ The name of the newly created sdist within ``wheel_directory``.
"""
return self._call_hook('build_sdist', {
'sdist_directory': abspath(sdist_directory),
@@ -292,29 +295,15 @@ class Pep517HookCaller(object):
})
def _call_hook(self, hook_name, kwargs):
- # On Python 2, pytoml returns Unicode values (which is correct) but the
- # environment passed to check_call needs to contain string values. We
- # convert here by encoding using ASCII (the backend can only contain
- # letters, digits and _, . and : characters, and will be used as a
- # Python identifier, so non-ASCII content is wrong on Python 2 in
- # any case).
- # For backend_path, we use sys.getfilesystemencoding.
- if sys.version_info[0] == 2:
- build_backend = self.build_backend.encode('ASCII')
- else:
- build_backend = self.build_backend
- extra_environ = {'PEP517_BUILD_BACKEND': build_backend}
+ extra_environ = {'PEP517_BUILD_BACKEND': self.build_backend}
if self.backend_path:
backend_path = os.pathsep.join(self.backend_path)
- if sys.version_info[0] == 2:
- backend_path = backend_path.encode(sys.getfilesystemencoding())
extra_environ['PEP517_BACKEND_PATH'] = backend_path
- with tempdir() as td:
+ with tempfile.TemporaryDirectory() as td:
hook_input = {'kwargs': kwargs}
- compat.write_json(hook_input, pjoin(td, 'input.json'),
- indent=2)
+ write_json(hook_input, pjoin(td, 'input.json'), indent=2)
# Run the hook in a subprocess
with _in_proc_script_path() as script:
@@ -325,7 +314,7 @@ class Pep517HookCaller(object):
extra_environ=extra_environ
)
- data = compat.read_json(pjoin(td, 'output.json'))
+ data = read_json(pjoin(td, 'output.json'))
if data.get('unsupported'):
raise UnsupportedOperation(data.get('traceback', ''))
if data.get('no_backend'):
@@ -339,37 +328,3 @@ class Pep517HookCaller(object):
if data.get('hook_missing'):
raise HookMissing(data.get('missing_hook_name') or hook_name)
return data['return_val']
-
-
-class LoggerWrapper(threading.Thread):
- """
- Read messages from a pipe and redirect them
- to a logger (see python's logging module).
- """
-
- def __init__(self, logger, level):
- threading.Thread.__init__(self)
- self.daemon = True
-
- self.logger = logger
- self.level = level
-
- # create the pipe and reader
- self.fd_read, self.fd_write = os.pipe()
- self.reader = os.fdopen(self.fd_read)
-
- self.start()
-
- def fileno(self):
- return self.fd_write
-
- @staticmethod
- def remove_newline(msg):
- return msg[:-1] if msg.endswith(os.linesep) else msg
-
- def run(self):
- for line in self.reader:
- self._write(self.remove_newline(line))
-
- def _write(self, message):
- self.logger.log(self.level, message)
diff --git a/src/pip/_vendor/pep517/in_process/__init__.py b/src/pip/_vendor/pyproject_hooks/_in_process/__init__.py
index c932313b3..917fa065b 100644
--- a/src/pip/_vendor/pep517/in_process/__init__.py
+++ b/src/pip/_vendor/pyproject_hooks/_in_process/__init__.py
@@ -3,15 +3,16 @@
The subpackage should stay as empty as possible to avoid shadowing modules that
the backend might import.
"""
-from os.path import dirname, abspath, join as pjoin
-from contextlib import contextmanager
-try:
- import importlib.resources as resources
+import importlib.resources as resources
+try:
+ resources.files
+except AttributeError:
+ # Python 3.8 compatibility
def _in_proc_script_path():
return resources.path(__package__, '_in_process.py')
-except ImportError:
- @contextmanager
+else:
def _in_proc_script_path():
- yield pjoin(dirname(abspath(__file__)), '_in_process.py')
+ return resources.as_file(
+ resources.files(__package__).joinpath('_in_process.py'))
diff --git a/src/pip/_vendor/pep517/in_process/_in_process.py b/src/pip/_vendor/pyproject_hooks/_in_process/_in_process.py
index 954a4ab05..ee511ff20 100644
--- a/src/pip/_vendor/pep517/in_process/_in_process.py
+++ b/src/pip/_vendor/pyproject_hooks/_in_process/_in_process.py
@@ -12,41 +12,29 @@ Results:
- control_dir/output.json
- {"return_val": ...}
"""
-from glob import glob
-from importlib import import_module
import json
import os
import os.path
-from os.path import join as pjoin
import re
import shutil
import sys
import traceback
+from glob import glob
+from importlib import import_module
+from os.path import join as pjoin
-# This file is run as a script, and `import compat` is not zip-safe, so we
-# include write_json() and read_json() from compat.py.
-#
-# Handle reading and writing JSON in UTF-8, on Python 3 and 2.
+# This file is run as a script, and `import wrappers` is not zip-safe, so we
+# include write_json() and read_json() from wrappers.py.
-if sys.version_info[0] >= 3:
- # Python 3
- def write_json(obj, path, **kwargs):
- with open(path, 'w', encoding='utf-8') as f:
- json.dump(obj, f, **kwargs)
- def read_json(path):
- with open(path, 'r', encoding='utf-8') as f:
- return json.load(f)
+def write_json(obj, path, **kwargs):
+ with open(path, 'w', encoding='utf-8') as f:
+ json.dump(obj, f, **kwargs)
-else:
- # Python 2
- def write_json(obj, path, **kwargs):
- with open(path, 'wb') as f:
- json.dump(obj, f, encoding='utf-8', **kwargs)
- def read_json(path):
- with open(path, 'rb') as f:
- return json.load(f)
+def read_json(path):
+ with open(path, encoding='utf-8') as f:
+ return json.load(f)
class BackendUnavailable(Exception):
@@ -64,7 +52,7 @@ class BackendInvalid(Exception):
class HookMissing(Exception):
"""Raised if a hook is missing and we are not executing the fallback"""
def __init__(self, hook_name=None):
- super(HookMissing, self).__init__(hook_name)
+ super().__init__(hook_name)
self.hook_name = hook_name
@@ -157,11 +145,13 @@ def prepare_metadata_for_build_wheel(
except AttributeError:
if not _allow_fallback:
raise HookMissing()
- whl_basename = backend.build_wheel(metadata_directory, config_settings)
- return _get_wheel_metadata_from_wheel(whl_basename, metadata_directory,
- config_settings)
else:
return hook(metadata_directory, config_settings)
+ # fallback to build_wheel outside the try block to avoid exception chaining
+ # which can be confusing to users and is not relevant
+ whl_basename = backend.build_wheel(metadata_directory, config_settings)
+ return _get_wheel_metadata_from_wheel(whl_basename, metadata_directory,
+ config_settings)
def prepare_metadata_for_build_editable(
diff --git a/src/pip/_vendor/requests/__init__.py b/src/pip/_vendor/requests/__init__.py
index 4f80e28fc..a47762480 100644
--- a/src/pip/_vendor/requests/__init__.py
+++ b/src/pip/_vendor/requests/__init__.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
# __
# /__) _ _ _ _ _/ _
# / ( (- (/ (/ (- _) / _)
@@ -40,8 +38,10 @@ is at <https://requests.readthedocs.io>.
:license: Apache 2.0, see LICENSE for more details.
"""
-from pip._vendor import urllib3
import warnings
+
+from pip._vendor import urllib3
+
from .exceptions import RequestsDependencyWarning
charset_normalizer_version = None
@@ -51,13 +51,14 @@ try:
except ImportError:
chardet_version = None
+
def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version):
- urllib3_version = urllib3_version.split('.')
- assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git.
+ urllib3_version = urllib3_version.split(".")
+ assert urllib3_version != ["dev"] # Verify urllib3 isn't installed from git.
# Sometimes, urllib3 only reports its version as 16.1.
if len(urllib3_version) == 2:
- urllib3_version.append('0')
+ urllib3_version.append("0")
# Check urllib3 for compatibility.
major, minor, patch = urllib3_version # noqa: F811
@@ -69,36 +70,46 @@ def check_compatibility(urllib3_version, chardet_version, charset_normalizer_ver
# Check charset_normalizer for compatibility.
if chardet_version:
- major, minor, patch = chardet_version.split('.')[:3]
+ major, minor, patch = chardet_version.split(".")[:3]
major, minor, patch = int(major), int(minor), int(patch)
- # chardet_version >= 3.0.2, < 5.0.0
- assert (3, 0, 2) <= (major, minor, patch) < (5, 0, 0)
+ # chardet_version >= 3.0.2, < 6.0.0
+ assert (3, 0, 2) <= (major, minor, patch) < (6, 0, 0)
elif charset_normalizer_version:
- major, minor, patch = charset_normalizer_version.split('.')[:3]
+ major, minor, patch = charset_normalizer_version.split(".")[:3]
major, minor, patch = int(major), int(minor), int(patch)
- # charset_normalizer >= 2.0.0 < 3.0.0
- assert (2, 0, 0) <= (major, minor, patch) < (3, 0, 0)
+ # charset_normalizer >= 2.0.0 < 4.0.0
+ assert (2, 0, 0) <= (major, minor, patch) < (4, 0, 0)
else:
raise Exception("You need either charset_normalizer or chardet installed")
+
def _check_cryptography(cryptography_version):
# cryptography < 1.3.4
try:
- cryptography_version = list(map(int, cryptography_version.split('.')))
+ cryptography_version = list(map(int, cryptography_version.split(".")))
except ValueError:
return
if cryptography_version < [1, 3, 4]:
- warning = 'Old version of cryptography ({}) may cause slowdown.'.format(cryptography_version)
+ warning = "Old version of cryptography ({}) may cause slowdown.".format(
+ cryptography_version
+ )
warnings.warn(warning, RequestsDependencyWarning)
+
# Check imported dependencies for compatibility.
try:
- check_compatibility(urllib3.__version__, chardet_version, charset_normalizer_version)
+ check_compatibility(
+ urllib3.__version__, chardet_version, charset_normalizer_version
+ )
except (AssertionError, ValueError):
- warnings.warn("urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported "
- "version!".format(urllib3.__version__, chardet_version, charset_normalizer_version),
- RequestsDependencyWarning)
+ warnings.warn(
+ "urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported "
+ "version!".format(
+ urllib3.__version__, chardet_version, charset_normalizer_version
+ ),
+ RequestsDependencyWarning,
+ )
# Attempt to enable urllib3's fallback for SNI support
# if the standard library doesn't support SNI or the
@@ -116,39 +127,56 @@ try:
if not getattr(ssl, "HAS_SNI", False):
from pip._vendor.urllib3.contrib import pyopenssl
+
pyopenssl.inject_into_urllib3()
# Check cryptography version
from cryptography import __version__ as cryptography_version
+
_check_cryptography(cryptography_version)
except ImportError:
pass
# urllib3's DependencyWarnings should be silenced.
from pip._vendor.urllib3.exceptions import DependencyWarning
-warnings.simplefilter('ignore', DependencyWarning)
-from .__version__ import __title__, __description__, __url__, __version__
-from .__version__ import __build__, __author__, __author_email__, __license__
-from .__version__ import __copyright__, __cake__
-
-from . import utils
-from . import packages
-from .models import Request, Response, PreparedRequest
-from .api import request, get, head, post, patch, put, delete, options
-from .sessions import session, Session
-from .status_codes import codes
-from .exceptions import (
- RequestException, Timeout, URLRequired,
- TooManyRedirects, HTTPError, ConnectionError,
- FileModeWarning, ConnectTimeout, ReadTimeout
-)
+warnings.simplefilter("ignore", DependencyWarning)
# Set default logging handler to avoid "No handler found" warnings.
import logging
from logging import NullHandler
+from . import packages, utils
+from .__version__ import (
+ __author__,
+ __author_email__,
+ __build__,
+ __cake__,
+ __copyright__,
+ __description__,
+ __license__,
+ __title__,
+ __url__,
+ __version__,
+)
+from .api import delete, get, head, options, patch, post, put, request
+from .exceptions import (
+ ConnectionError,
+ ConnectTimeout,
+ FileModeWarning,
+ HTTPError,
+ JSONDecodeError,
+ ReadTimeout,
+ RequestException,
+ Timeout,
+ TooManyRedirects,
+ URLRequired,
+)
+from .models import PreparedRequest, Request, Response
+from .sessions import Session, session
+from .status_codes import codes
+
logging.getLogger(__name__).addHandler(NullHandler())
# FileModeWarnings go off per the default.
-warnings.simplefilter('default', FileModeWarning, append=True)
+warnings.simplefilter("default", FileModeWarning, append=True)
diff --git a/src/pip/_vendor/requests/__version__.py b/src/pip/_vendor/requests/__version__.py
index 0d7cde1df..69be3dec7 100644
--- a/src/pip/_vendor/requests/__version__.py
+++ b/src/pip/_vendor/requests/__version__.py
@@ -2,13 +2,13 @@
# |( |- |.| | | |- `-. | `-.
# ' ' `-' `-`.`-' `-' `-' ' `-'
-__title__ = 'requests'
-__description__ = 'Python HTTP for Humans.'
-__url__ = 'https://requests.readthedocs.io'
-__version__ = '2.26.0'
-__build__ = 0x022600
-__author__ = 'Kenneth Reitz'
-__author_email__ = 'me@kennethreitz.org'
-__license__ = 'Apache 2.0'
-__copyright__ = 'Copyright 2020 Kenneth Reitz'
-__cake__ = u'\u2728 \U0001f370 \u2728'
+__title__ = "requests"
+__description__ = "Python HTTP for Humans."
+__url__ = "https://requests.readthedocs.io"
+__version__ = "2.28.2"
+__build__ = 0x022802
+__author__ = "Kenneth Reitz"
+__author_email__ = "me@kennethreitz.org"
+__license__ = "Apache 2.0"
+__copyright__ = "Copyright Kenneth Reitz"
+__cake__ = "\u2728 \U0001f370 \u2728"
diff --git a/src/pip/_vendor/requests/_internal_utils.py b/src/pip/_vendor/requests/_internal_utils.py
index 759d9a56b..7dc9bc533 100644
--- a/src/pip/_vendor/requests/_internal_utils.py
+++ b/src/pip/_vendor/requests/_internal_utils.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
"""
requests._internal_utils
~~~~~~~~~~~~~~
@@ -7,11 +5,22 @@ requests._internal_utils
Provides utility functions that are consumed internally by Requests
which depend on extremely few external helpers (such as compat)
"""
+import re
+
+from .compat import builtin_str
+
+_VALID_HEADER_NAME_RE_BYTE = re.compile(rb"^[^:\s][^:\r\n]*$")
+_VALID_HEADER_NAME_RE_STR = re.compile(r"^[^:\s][^:\r\n]*$")
+_VALID_HEADER_VALUE_RE_BYTE = re.compile(rb"^\S[^\r\n]*$|^$")
+_VALID_HEADER_VALUE_RE_STR = re.compile(r"^\S[^\r\n]*$|^$")
-from .compat import is_py2, builtin_str, str
+HEADER_VALIDATORS = {
+ bytes: (_VALID_HEADER_NAME_RE_BYTE, _VALID_HEADER_VALUE_RE_BYTE),
+ str: (_VALID_HEADER_NAME_RE_STR, _VALID_HEADER_VALUE_RE_STR),
+}
-def to_native_string(string, encoding='ascii'):
+def to_native_string(string, encoding="ascii"):
"""Given a string object, regardless of type, returns a representation of
that string in the native string type, encoding and decoding where
necessary. This assumes ASCII unless told otherwise.
@@ -19,10 +28,7 @@ def to_native_string(string, encoding='ascii'):
if isinstance(string, builtin_str):
out = string
else:
- if is_py2:
- out = string.encode(encoding)
- else:
- out = string.decode(encoding)
+ out = string.decode(encoding)
return out
@@ -36,7 +42,7 @@ def unicode_is_ascii(u_string):
"""
assert isinstance(u_string, str)
try:
- u_string.encode('ascii')
+ u_string.encode("ascii")
return True
except UnicodeEncodeError:
return False
diff --git a/src/pip/_vendor/requests/adapters.py b/src/pip/_vendor/requests/adapters.py
index c30e7c92d..f68f7d467 100644
--- a/src/pip/_vendor/requests/adapters.py
+++ b/src/pip/_vendor/requests/adapters.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
"""
requests.adapters
~~~~~~~~~~~~~~~~~
@@ -9,57 +7,76 @@ and maintain connections.
"""
import os.path
-import socket
+import socket # noqa: F401
+from pip._vendor.urllib3.exceptions import ClosedPoolError, ConnectTimeoutError
+from pip._vendor.urllib3.exceptions import HTTPError as _HTTPError
+from pip._vendor.urllib3.exceptions import InvalidHeader as _InvalidHeader
+from pip._vendor.urllib3.exceptions import (
+ LocationValueError,
+ MaxRetryError,
+ NewConnectionError,
+ ProtocolError,
+)
+from pip._vendor.urllib3.exceptions import ProxyError as _ProxyError
+from pip._vendor.urllib3.exceptions import ReadTimeoutError, ResponseError
+from pip._vendor.urllib3.exceptions import SSLError as _SSLError
from pip._vendor.urllib3.poolmanager import PoolManager, proxy_from_url
from pip._vendor.urllib3.response import HTTPResponse
-from pip._vendor.urllib3.util import parse_url
from pip._vendor.urllib3.util import Timeout as TimeoutSauce
+from pip._vendor.urllib3.util import parse_url
from pip._vendor.urllib3.util.retry import Retry
-from pip._vendor.urllib3.exceptions import ClosedPoolError
-from pip._vendor.urllib3.exceptions import ConnectTimeoutError
-from pip._vendor.urllib3.exceptions import HTTPError as _HTTPError
-from pip._vendor.urllib3.exceptions import MaxRetryError
-from pip._vendor.urllib3.exceptions import NewConnectionError
-from pip._vendor.urllib3.exceptions import ProxyError as _ProxyError
-from pip._vendor.urllib3.exceptions import ProtocolError
-from pip._vendor.urllib3.exceptions import ReadTimeoutError
-from pip._vendor.urllib3.exceptions import SSLError as _SSLError
-from pip._vendor.urllib3.exceptions import ResponseError
-from pip._vendor.urllib3.exceptions import LocationValueError
+from .auth import _basic_auth_str
+from .compat import basestring, urlparse
+from .cookies import extract_cookies_to_jar
+from .exceptions import (
+ ConnectionError,
+ ConnectTimeout,
+ InvalidHeader,
+ InvalidProxyURL,
+ InvalidSchema,
+ InvalidURL,
+ ProxyError,
+ ReadTimeout,
+ RetryError,
+ SSLError,
+)
from .models import Response
-from .compat import urlparse, basestring
-from .utils import (DEFAULT_CA_BUNDLE_PATH, extract_zipped_paths,
- get_encoding_from_headers, prepend_scheme_if_needed,
- get_auth_from_url, urldefragauth, select_proxy)
from .structures import CaseInsensitiveDict
-from .cookies import extract_cookies_to_jar
-from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError,
- ProxyError, RetryError, InvalidSchema, InvalidProxyURL,
- InvalidURL)
-from .auth import _basic_auth_str
+from .utils import (
+ DEFAULT_CA_BUNDLE_PATH,
+ extract_zipped_paths,
+ get_auth_from_url,
+ get_encoding_from_headers,
+ prepend_scheme_if_needed,
+ select_proxy,
+ urldefragauth,
+)
try:
from pip._vendor.urllib3.contrib.socks import SOCKSProxyManager
except ImportError:
+
def SOCKSProxyManager(*args, **kwargs):
raise InvalidSchema("Missing dependencies for SOCKS support.")
+
DEFAULT_POOLBLOCK = False
DEFAULT_POOLSIZE = 10
DEFAULT_RETRIES = 0
DEFAULT_POOL_TIMEOUT = None
-class BaseAdapter(object):
+class BaseAdapter:
"""The Base Transport Adapter"""
def __init__(self):
- super(BaseAdapter, self).__init__()
+ super().__init__()
- def send(self, request, stream=False, timeout=None, verify=True,
- cert=None, proxies=None):
+ def send(
+ self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
+ ):
"""Sends PreparedRequest object. Returns Response object.
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
@@ -107,12 +124,22 @@ class HTTPAdapter(BaseAdapter):
>>> a = requests.adapters.HTTPAdapter(max_retries=3)
>>> s.mount('http://', a)
"""
- __attrs__ = ['max_retries', 'config', '_pool_connections', '_pool_maxsize',
- '_pool_block']
- def __init__(self, pool_connections=DEFAULT_POOLSIZE,
- pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES,
- pool_block=DEFAULT_POOLBLOCK):
+ __attrs__ = [
+ "max_retries",
+ "config",
+ "_pool_connections",
+ "_pool_maxsize",
+ "_pool_block",
+ ]
+
+ def __init__(
+ self,
+ pool_connections=DEFAULT_POOLSIZE,
+ pool_maxsize=DEFAULT_POOLSIZE,
+ max_retries=DEFAULT_RETRIES,
+ pool_block=DEFAULT_POOLBLOCK,
+ ):
if max_retries == DEFAULT_RETRIES:
self.max_retries = Retry(0, read=False)
else:
@@ -120,7 +147,7 @@ class HTTPAdapter(BaseAdapter):
self.config = {}
self.proxy_manager = {}
- super(HTTPAdapter, self).__init__()
+ super().__init__()
self._pool_connections = pool_connections
self._pool_maxsize = pool_maxsize
@@ -140,10 +167,13 @@ class HTTPAdapter(BaseAdapter):
for attr, value in state.items():
setattr(self, attr, value)
- self.init_poolmanager(self._pool_connections, self._pool_maxsize,
- block=self._pool_block)
+ self.init_poolmanager(
+ self._pool_connections, self._pool_maxsize, block=self._pool_block
+ )
- def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs):
+ def init_poolmanager(
+ self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs
+ ):
"""Initializes a urllib3 PoolManager.
This method should not be called from user code, and is only
@@ -160,8 +190,13 @@ class HTTPAdapter(BaseAdapter):
self._pool_maxsize = maxsize
self._pool_block = block
- self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize,
- block=block, strict=True, **pool_kwargs)
+ self.poolmanager = PoolManager(
+ num_pools=connections,
+ maxsize=maxsize,
+ block=block,
+ strict=True,
+ **pool_kwargs,
+ )
def proxy_manager_for(self, proxy, **proxy_kwargs):
"""Return urllib3 ProxyManager for the given proxy.
@@ -177,7 +212,7 @@ class HTTPAdapter(BaseAdapter):
"""
if proxy in self.proxy_manager:
manager = self.proxy_manager[proxy]
- elif proxy.lower().startswith('socks'):
+ elif proxy.lower().startswith("socks"):
username, password = get_auth_from_url(proxy)
manager = self.proxy_manager[proxy] = SOCKSProxyManager(
proxy,
@@ -186,7 +221,7 @@ class HTTPAdapter(BaseAdapter):
num_pools=self._pool_connections,
maxsize=self._pool_maxsize,
block=self._pool_block,
- **proxy_kwargs
+ **proxy_kwargs,
)
else:
proxy_headers = self.proxy_headers(proxy)
@@ -196,7 +231,8 @@ class HTTPAdapter(BaseAdapter):
num_pools=self._pool_connections,
maxsize=self._pool_maxsize,
block=self._pool_block,
- **proxy_kwargs)
+ **proxy_kwargs,
+ )
return manager
@@ -212,7 +248,7 @@ class HTTPAdapter(BaseAdapter):
to a CA bundle to use
:param cert: The SSL certificate to verify.
"""
- if url.lower().startswith('https') and verify:
+ if url.lower().startswith("https") and verify:
cert_loc = None
@@ -224,17 +260,19 @@ class HTTPAdapter(BaseAdapter):
cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH)
if not cert_loc or not os.path.exists(cert_loc):
- raise IOError("Could not find a suitable TLS CA certificate bundle, "
- "invalid path: {}".format(cert_loc))
+ raise OSError(
+ f"Could not find a suitable TLS CA certificate bundle, "
+ f"invalid path: {cert_loc}"
+ )
- conn.cert_reqs = 'CERT_REQUIRED'
+ conn.cert_reqs = "CERT_REQUIRED"
if not os.path.isdir(cert_loc):
conn.ca_certs = cert_loc
else:
conn.ca_cert_dir = cert_loc
else:
- conn.cert_reqs = 'CERT_NONE'
+ conn.cert_reqs = "CERT_NONE"
conn.ca_certs = None
conn.ca_cert_dir = None
@@ -246,11 +284,14 @@ class HTTPAdapter(BaseAdapter):
conn.cert_file = cert
conn.key_file = None
if conn.cert_file and not os.path.exists(conn.cert_file):
- raise IOError("Could not find the TLS certificate file, "
- "invalid path: {}".format(conn.cert_file))
+ raise OSError(
+ f"Could not find the TLS certificate file, "
+ f"invalid path: {conn.cert_file}"
+ )
if conn.key_file and not os.path.exists(conn.key_file):
- raise IOError("Could not find the TLS key file, "
- "invalid path: {}".format(conn.key_file))
+ raise OSError(
+ f"Could not find the TLS key file, invalid path: {conn.key_file}"
+ )
def build_response(self, req, resp):
"""Builds a :class:`Response <requests.Response>` object from a urllib3
@@ -265,10 +306,10 @@ class HTTPAdapter(BaseAdapter):
response = Response()
# Fallback to None if there's no status_code, for whatever reason.
- response.status_code = getattr(resp, 'status', None)
+ response.status_code = getattr(resp, "status", None)
# Make headers case-insensitive.
- response.headers = CaseInsensitiveDict(getattr(resp, 'headers', {}))
+ response.headers = CaseInsensitiveDict(getattr(resp, "headers", {}))
# Set encoding.
response.encoding = get_encoding_from_headers(response.headers)
@@ -276,7 +317,7 @@ class HTTPAdapter(BaseAdapter):
response.reason = response.raw.reason
if isinstance(req.url, bytes):
- response.url = req.url.decode('utf-8')
+ response.url = req.url.decode("utf-8")
else:
response.url = req.url
@@ -301,11 +342,13 @@ class HTTPAdapter(BaseAdapter):
proxy = select_proxy(url, proxies)
if proxy:
- proxy = prepend_scheme_if_needed(proxy, 'http')
+ proxy = prepend_scheme_if_needed(proxy, "http")
proxy_url = parse_url(proxy)
if not proxy_url.host:
- raise InvalidProxyURL("Please check proxy URL. It is malformed"
- " and could be missing the host.")
+ raise InvalidProxyURL(
+ "Please check proxy URL. It is malformed "
+ "and could be missing the host."
+ )
proxy_manager = self.proxy_manager_for(proxy)
conn = proxy_manager.connection_from_url(url)
else:
@@ -343,11 +386,11 @@ class HTTPAdapter(BaseAdapter):
proxy = select_proxy(request.url, proxies)
scheme = urlparse(request.url).scheme
- is_proxied_http_request = (proxy and scheme != 'https')
+ is_proxied_http_request = proxy and scheme != "https"
using_socks_proxy = False
if proxy:
proxy_scheme = urlparse(proxy).scheme.lower()
- using_socks_proxy = proxy_scheme.startswith('socks')
+ using_socks_proxy = proxy_scheme.startswith("socks")
url = request.path_url
if is_proxied_http_request and not using_socks_proxy:
@@ -386,12 +429,13 @@ class HTTPAdapter(BaseAdapter):
username, password = get_auth_from_url(proxy)
if username:
- headers['Proxy-Authorization'] = _basic_auth_str(username,
- password)
+ headers["Proxy-Authorization"] = _basic_auth_str(username, password)
return headers
- def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):
+ def send(
+ self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
+ ):
"""Sends PreparedRequest object. Returns Response object.
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
@@ -415,20 +459,26 @@ class HTTPAdapter(BaseAdapter):
self.cert_verify(conn, request.url, verify, cert)
url = self.request_url(request, proxies)
- self.add_headers(request, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies)
+ self.add_headers(
+ request,
+ stream=stream,
+ timeout=timeout,
+ verify=verify,
+ cert=cert,
+ proxies=proxies,
+ )
- chunked = not (request.body is None or 'Content-Length' in request.headers)
+ chunked = not (request.body is None or "Content-Length" in request.headers)
if isinstance(timeout, tuple):
try:
connect, read = timeout
timeout = TimeoutSauce(connect=connect, read=read)
- except ValueError as e:
- # this may raise a string formatting error.
- err = ("Invalid timeout {}. Pass a (connect, read) "
- "timeout tuple, or a single float to set "
- "both timeouts to the same value".format(timeout))
- raise ValueError(err)
+ except ValueError:
+ raise ValueError(
+ f"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, "
+ f"or a single float to set both timeouts to the same value."
+ )
elif isinstance(timeout, TimeoutSauce):
pass
else:
@@ -446,20 +496,24 @@ class HTTPAdapter(BaseAdapter):
preload_content=False,
decode_content=False,
retries=self.max_retries,
- timeout=timeout
+ timeout=timeout,
)
# Send the request.
else:
- if hasattr(conn, 'proxy_pool'):
+ if hasattr(conn, "proxy_pool"):
conn = conn.proxy_pool
low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)
try:
- low_conn.putrequest(request.method,
- url,
- skip_accept_encoding=True)
+ skip_host = "Host" in request.headers
+ low_conn.putrequest(
+ request.method,
+ url,
+ skip_accept_encoding=True,
+ skip_host=skip_host,
+ )
for header, value in request.headers.items():
low_conn.putheader(header, value)
@@ -467,34 +521,29 @@ class HTTPAdapter(BaseAdapter):
low_conn.endheaders()
for i in request.body:
- low_conn.send(hex(len(i))[2:].encode('utf-8'))
- low_conn.send(b'\r\n')
+ low_conn.send(hex(len(i))[2:].encode("utf-8"))
+ low_conn.send(b"\r\n")
low_conn.send(i)
- low_conn.send(b'\r\n')
- low_conn.send(b'0\r\n\r\n')
+ low_conn.send(b"\r\n")
+ low_conn.send(b"0\r\n\r\n")
# Receive the response from the server
- try:
- # For Python 2.7, use buffering of HTTP responses
- r = low_conn.getresponse(buffering=True)
- except TypeError:
- # For compatibility with Python 3.3+
- r = low_conn.getresponse()
+ r = low_conn.getresponse()
resp = HTTPResponse.from_httplib(
r,
pool=conn,
connection=low_conn,
preload_content=False,
- decode_content=False
+ decode_content=False,
)
- except:
+ except Exception:
# If we hit any problems here, clean up the connection.
- # Then, reraise so that we can handle the actual exception.
+ # Then, raise so that we can handle the actual exception.
low_conn.close()
raise
- except (ProtocolError, socket.error) as err:
+ except (ProtocolError, OSError) as err:
raise ConnectionError(err, request=request)
except MaxRetryError as e:
@@ -527,6 +576,8 @@ class HTTPAdapter(BaseAdapter):
raise SSLError(e, request=request)
elif isinstance(e, ReadTimeoutError):
raise ReadTimeout(e, request=request)
+ elif isinstance(e, _InvalidHeader):
+ raise InvalidHeader(e, request=request)
else:
raise
diff --git a/src/pip/_vendor/requests/api.py b/src/pip/_vendor/requests/api.py
index 4cba90eef..2f71aaed1 100644
--- a/src/pip/_vendor/requests/api.py
+++ b/src/pip/_vendor/requests/api.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
"""
requests.api
~~~~~~~~~~~~
@@ -72,7 +70,7 @@ def get(url, params=None, **kwargs):
:rtype: requests.Response
"""
- return request('get', url, params=params, **kwargs)
+ return request("get", url, params=params, **kwargs)
def options(url, **kwargs):
@@ -84,7 +82,7 @@ def options(url, **kwargs):
:rtype: requests.Response
"""
- return request('options', url, **kwargs)
+ return request("options", url, **kwargs)
def head(url, **kwargs):
@@ -98,8 +96,8 @@ def head(url, **kwargs):
:rtype: requests.Response
"""
- kwargs.setdefault('allow_redirects', False)
- return request('head', url, **kwargs)
+ kwargs.setdefault("allow_redirects", False)
+ return request("head", url, **kwargs)
def post(url, data=None, json=None, **kwargs):
@@ -114,7 +112,7 @@ def post(url, data=None, json=None, **kwargs):
:rtype: requests.Response
"""
- return request('post', url, data=data, json=json, **kwargs)
+ return request("post", url, data=data, json=json, **kwargs)
def put(url, data=None, **kwargs):
@@ -129,7 +127,7 @@ def put(url, data=None, **kwargs):
:rtype: requests.Response
"""
- return request('put', url, data=data, **kwargs)
+ return request("put", url, data=data, **kwargs)
def patch(url, data=None, **kwargs):
@@ -144,7 +142,7 @@ def patch(url, data=None, **kwargs):
:rtype: requests.Response
"""
- return request('patch', url, data=data, **kwargs)
+ return request("patch", url, data=data, **kwargs)
def delete(url, **kwargs):
@@ -156,4 +154,4 @@ def delete(url, **kwargs):
:rtype: requests.Response
"""
- return request('delete', url, **kwargs)
+ return request("delete", url, **kwargs)
diff --git a/src/pip/_vendor/requests/auth.py b/src/pip/_vendor/requests/auth.py
index eeface39a..9733686dd 100644
--- a/src/pip/_vendor/requests/auth.py
+++ b/src/pip/_vendor/requests/auth.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
"""
requests.auth
~~~~~~~~~~~~~
@@ -7,22 +5,21 @@ requests.auth
This module contains the authentication handlers for Requests.
"""
+import hashlib
import os
import re
-import time
-import hashlib
import threading
+import time
import warnings
-
from base64 import b64encode
-from .compat import urlparse, str, basestring
-from .cookies import extract_cookies_to_jar
from ._internal_utils import to_native_string
+from .compat import basestring, str, urlparse
+from .cookies import extract_cookies_to_jar
from .utils import parse_dict_header
-CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
-CONTENT_TYPE_MULTI_PART = 'multipart/form-data'
+CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"
+CONTENT_TYPE_MULTI_PART = "multipart/form-data"
def _basic_auth_str(username, password):
@@ -57,23 +54,23 @@ def _basic_auth_str(username, password):
# -- End Removal --
if isinstance(username, str):
- username = username.encode('latin1')
+ username = username.encode("latin1")
if isinstance(password, str):
- password = password.encode('latin1')
+ password = password.encode("latin1")
- authstr = 'Basic ' + to_native_string(
- b64encode(b':'.join((username, password))).strip()
+ authstr = "Basic " + to_native_string(
+ b64encode(b":".join((username, password))).strip()
)
return authstr
-class AuthBase(object):
+class AuthBase:
"""Base class that all auth implementations derive from"""
def __call__(self, r):
- raise NotImplementedError('Auth hooks must be callable.')
+ raise NotImplementedError("Auth hooks must be callable.")
class HTTPBasicAuth(AuthBase):
@@ -84,16 +81,18 @@ class HTTPBasicAuth(AuthBase):
self.password = password
def __eq__(self, other):
- return all([
- self.username == getattr(other, 'username', None),
- self.password == getattr(other, 'password', None)
- ])
+ return all(
+ [
+ self.username == getattr(other, "username", None),
+ self.password == getattr(other, "password", None),
+ ]
+ )
def __ne__(self, other):
return not self == other
def __call__(self, r):
- r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
+ r.headers["Authorization"] = _basic_auth_str(self.username, self.password)
return r
@@ -101,7 +100,7 @@ class HTTPProxyAuth(HTTPBasicAuth):
"""Attaches HTTP Proxy Authentication to a given Request object."""
def __call__(self, r):
- r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password)
+ r.headers["Proxy-Authorization"] = _basic_auth_str(self.username, self.password)
return r
@@ -116,9 +115,9 @@ class HTTPDigestAuth(AuthBase):
def init_per_thread_state(self):
# Ensure state is initialized just once per-thread
- if not hasattr(self._thread_local, 'init'):
+ if not hasattr(self._thread_local, "init"):
self._thread_local.init = True
- self._thread_local.last_nonce = ''
+ self._thread_local.last_nonce = ""
self._thread_local.nonce_count = 0
self._thread_local.chal = {}
self._thread_local.pos = None
@@ -129,44 +128,52 @@ class HTTPDigestAuth(AuthBase):
:rtype: str
"""
- realm = self._thread_local.chal['realm']
- nonce = self._thread_local.chal['nonce']
- qop = self._thread_local.chal.get('qop')
- algorithm = self._thread_local.chal.get('algorithm')
- opaque = self._thread_local.chal.get('opaque')
+ realm = self._thread_local.chal["realm"]
+ nonce = self._thread_local.chal["nonce"]
+ qop = self._thread_local.chal.get("qop")
+ algorithm = self._thread_local.chal.get("algorithm")
+ opaque = self._thread_local.chal.get("opaque")
hash_utf8 = None
if algorithm is None:
- _algorithm = 'MD5'
+ _algorithm = "MD5"
else:
_algorithm = algorithm.upper()
# lambdas assume digest modules are imported at the top level
- if _algorithm == 'MD5' or _algorithm == 'MD5-SESS':
+ if _algorithm == "MD5" or _algorithm == "MD5-SESS":
+
def md5_utf8(x):
if isinstance(x, str):
- x = x.encode('utf-8')
+ x = x.encode("utf-8")
return hashlib.md5(x).hexdigest()
+
hash_utf8 = md5_utf8
- elif _algorithm == 'SHA':
+ elif _algorithm == "SHA":
+
def sha_utf8(x):
if isinstance(x, str):
- x = x.encode('utf-8')
+ x = x.encode("utf-8")
return hashlib.sha1(x).hexdigest()
+
hash_utf8 = sha_utf8
- elif _algorithm == 'SHA-256':
+ elif _algorithm == "SHA-256":
+
def sha256_utf8(x):
if isinstance(x, str):
- x = x.encode('utf-8')
+ x = x.encode("utf-8")
return hashlib.sha256(x).hexdigest()
+
hash_utf8 = sha256_utf8
- elif _algorithm == 'SHA-512':
+ elif _algorithm == "SHA-512":
+
def sha512_utf8(x):
if isinstance(x, str):
- x = x.encode('utf-8')
+ x = x.encode("utf-8")
return hashlib.sha512(x).hexdigest()
+
hash_utf8 = sha512_utf8
- KD = lambda s, d: hash_utf8("%s:%s" % (s, d))
+ KD = lambda s, d: hash_utf8(f"{s}:{d}") # noqa:E731
if hash_utf8 is None:
return None
@@ -177,10 +184,10 @@ class HTTPDigestAuth(AuthBase):
#: path is request-uri defined in RFC 2616 which should not be empty
path = p_parsed.path or "/"
if p_parsed.query:
- path += '?' + p_parsed.query
+ path += f"?{p_parsed.query}"
- A1 = '%s:%s:%s' % (self.username, realm, self.password)
- A2 = '%s:%s' % (method, path)
+ A1 = f"{self.username}:{realm}:{self.password}"
+ A2 = f"{method}:{path}"
HA1 = hash_utf8(A1)
HA2 = hash_utf8(A2)
@@ -189,22 +196,20 @@ class HTTPDigestAuth(AuthBase):
self._thread_local.nonce_count += 1
else:
self._thread_local.nonce_count = 1
- ncvalue = '%08x' % self._thread_local.nonce_count
- s = str(self._thread_local.nonce_count).encode('utf-8')
- s += nonce.encode('utf-8')
- s += time.ctime().encode('utf-8')
+ ncvalue = f"{self._thread_local.nonce_count:08x}"
+ s = str(self._thread_local.nonce_count).encode("utf-8")
+ s += nonce.encode("utf-8")
+ s += time.ctime().encode("utf-8")
s += os.urandom(8)
- cnonce = (hashlib.sha1(s).hexdigest()[:16])
- if _algorithm == 'MD5-SESS':
- HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce))
+ cnonce = hashlib.sha1(s).hexdigest()[:16]
+ if _algorithm == "MD5-SESS":
+ HA1 = hash_utf8(f"{HA1}:{nonce}:{cnonce}")
if not qop:
- respdig = KD(HA1, "%s:%s" % (nonce, HA2))
- elif qop == 'auth' or 'auth' in qop.split(','):
- noncebit = "%s:%s:%s:%s:%s" % (
- nonce, ncvalue, cnonce, 'auth', HA2
- )
+ respdig = KD(HA1, f"{nonce}:{HA2}")
+ elif qop == "auth" or "auth" in qop.split(","):
+ noncebit = f"{nonce}:{ncvalue}:{cnonce}:auth:{HA2}"
respdig = KD(HA1, noncebit)
else:
# XXX handle auth-int.
@@ -213,18 +218,20 @@ class HTTPDigestAuth(AuthBase):
self._thread_local.last_nonce = nonce
# XXX should the partial digests be encoded too?
- base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
- 'response="%s"' % (self.username, realm, nonce, path, respdig)
+ base = (
+ f'username="{self.username}", realm="{realm}", nonce="{nonce}", '
+ f'uri="{path}", response="{respdig}"'
+ )
if opaque:
- base += ', opaque="%s"' % opaque
+ base += f', opaque="{opaque}"'
if algorithm:
- base += ', algorithm="%s"' % algorithm
+ base += f', algorithm="{algorithm}"'
if entdig:
- base += ', digest="%s"' % entdig
+ base += f', digest="{entdig}"'
if qop:
- base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce)
+ base += f', qop="auth", nc={ncvalue}, cnonce="{cnonce}"'
- return 'Digest %s' % (base)
+ return f"Digest {base}"
def handle_redirect(self, r, **kwargs):
"""Reset num_401_calls counter on redirects."""
@@ -248,13 +255,13 @@ class HTTPDigestAuth(AuthBase):
# Rewind the file position indicator of the body to where
# it was to resend the request.
r.request.body.seek(self._thread_local.pos)
- s_auth = r.headers.get('www-authenticate', '')
+ s_auth = r.headers.get("www-authenticate", "")
- if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2:
+ if "digest" in s_auth.lower() and self._thread_local.num_401_calls < 2:
self._thread_local.num_401_calls += 1
- pat = re.compile(r'digest ', flags=re.IGNORECASE)
- self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1))
+ pat = re.compile(r"digest ", flags=re.IGNORECASE)
+ self._thread_local.chal = parse_dict_header(pat.sub("", s_auth, count=1))
# Consume content and release the original connection
# to allow our new request to reuse the same one.
@@ -264,8 +271,9 @@ class HTTPDigestAuth(AuthBase):
extract_cookies_to_jar(prep._cookies, r.request, r.raw)
prep.prepare_cookies(prep._cookies)
- prep.headers['Authorization'] = self.build_digest_header(
- prep.method, prep.url)
+ prep.headers["Authorization"] = self.build_digest_header(
+ prep.method, prep.url
+ )
_r = r.connection.send(prep, **kwargs)
_r.history.append(r)
_r.request = prep
@@ -280,7 +288,7 @@ class HTTPDigestAuth(AuthBase):
self.init_per_thread_state()
# If we have a saved nonce, skip the 401
if self._thread_local.last_nonce:
- r.headers['Authorization'] = self.build_digest_header(r.method, r.url)
+ r.headers["Authorization"] = self.build_digest_header(r.method, r.url)
try:
self._thread_local.pos = r.body.tell()
except AttributeError:
@@ -289,17 +297,19 @@ class HTTPDigestAuth(AuthBase):
# file position of the previous body. Ensure it's set to
# None.
self._thread_local.pos = None
- r.register_hook('response', self.handle_401)
- r.register_hook('response', self.handle_redirect)
+ r.register_hook("response", self.handle_401)
+ r.register_hook("response", self.handle_redirect)
self._thread_local.num_401_calls = 1
return r
def __eq__(self, other):
- return all([
- self.username == getattr(other, 'username', None),
- self.password == getattr(other, 'password', None)
- ])
+ return all(
+ [
+ self.username == getattr(other, "username", None),
+ self.password == getattr(other, "password", None),
+ ]
+ )
def __ne__(self, other):
return not self == other
diff --git a/src/pip/_vendor/requests/certs.py b/src/pip/_vendor/requests/certs.py
index 06a594e58..38696a1fb 100644
--- a/src/pip/_vendor/requests/certs.py
+++ b/src/pip/_vendor/requests/certs.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# -*- coding: utf-8 -*-
"""
requests.certs
@@ -12,7 +11,14 @@ If you are packaging Requests, e.g., for a Linux distribution or a managed
environment, you can change the definition of where() to return a separately
packaged CA bundle.
"""
-from pip._vendor.certifi import where
-if __name__ == '__main__':
+import os
+
+if "_PIP_STANDALONE_CERT" not in os.environ:
+ from pip._vendor.certifi import where
+else:
+ def where():
+ return os.environ["_PIP_STANDALONE_CERT"]
+
+if __name__ == "__main__":
print(where())
diff --git a/src/pip/_vendor/requests/compat.py b/src/pip/_vendor/requests/compat.py
index 9e2937167..9ab2bb486 100644
--- a/src/pip/_vendor/requests/compat.py
+++ b/src/pip/_vendor/requests/compat.py
@@ -1,11 +1,10 @@
-# -*- coding: utf-8 -*-
-
"""
requests.compat
~~~~~~~~~~~~~~~
-This module handles import compatibility issues between Python 2 and
-Python 3.
+This module previously handled import compatibility issues
+between Python 2 and Python 3. It remains for backwards
+compatibility until the next major version.
"""
from pip._vendor import chardet
@@ -20,57 +19,49 @@ import sys
_ver = sys.version_info
#: Python 2.x?
-is_py2 = (_ver[0] == 2)
+is_py2 = _ver[0] == 2
#: Python 3.x?
-is_py3 = (_ver[0] == 3)
+is_py3 = _ver[0] == 3
# Note: We've patched out simplejson support in pip because it prevents
# upgrading simplejson on Windows.
-# try:
-# import simplejson as json
-# except (ImportError, SyntaxError):
-# # simplejson does not support Python 3.2, it throws a SyntaxError
-# # because of u'...' Unicode literals.
import json
+from json import JSONDecodeError
-# ---------
-# Specifics
-# ---------
-
-if is_py2:
- from urllib import (
- quote, unquote, quote_plus, unquote_plus, urlencode, getproxies,
- proxy_bypass, proxy_bypass_environment, getproxies_environment)
- from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag
- from urllib2 import parse_http_list
- import cookielib
- from Cookie import Morsel
- from StringIO import StringIO
- # Keep OrderedDict for backwards compatibility.
- from collections import Callable, Mapping, MutableMapping, OrderedDict
-
-
- builtin_str = str
- bytes = str
- str = unicode
- basestring = basestring
- numeric_types = (int, long, float)
- integer_types = (int, long)
+# Keep OrderedDict for backwards compatibility.
+from collections import OrderedDict
+from collections.abc import Callable, Mapping, MutableMapping
+from http import cookiejar as cookielib
+from http.cookies import Morsel
+from io import StringIO
-elif is_py3:
- from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
- from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment
- from http import cookiejar as cookielib
- from http.cookies import Morsel
- from io import StringIO
- # Keep OrderedDict for backwards compatibility.
- from collections import OrderedDict
- from collections.abc import Callable, Mapping, MutableMapping
+# --------------
+# Legacy Imports
+# --------------
+from urllib.parse import (
+ quote,
+ quote_plus,
+ unquote,
+ unquote_plus,
+ urldefrag,
+ urlencode,
+ urljoin,
+ urlparse,
+ urlsplit,
+ urlunparse,
+)
+from urllib.request import (
+ getproxies,
+ getproxies_environment,
+ parse_http_list,
+ proxy_bypass,
+ proxy_bypass_environment,
+)
- builtin_str = str
- str = str
- bytes = bytes
- basestring = (str, bytes)
- numeric_types = (int, float)
- integer_types = (int,)
+builtin_str = str
+str = str
+bytes = bytes
+basestring = (str, bytes)
+numeric_types = (int, float)
+integer_types = (int,)
diff --git a/src/pip/_vendor/requests/cookies.py b/src/pip/_vendor/requests/cookies.py
index 56fccd9c2..bf54ab237 100644
--- a/src/pip/_vendor/requests/cookies.py
+++ b/src/pip/_vendor/requests/cookies.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
"""
requests.cookies
~~~~~~~~~~~~~~~~
@@ -9,12 +7,12 @@ Compatibility code to be able to use `cookielib.CookieJar` with requests.
requests.utils imports from here, so be careful with imports.
"""
+import calendar
import copy
import time
-import calendar
from ._internal_utils import to_native_string
-from .compat import cookielib, urlparse, urlunparse, Morsel, MutableMapping
+from .compat import Morsel, MutableMapping, cookielib, urlparse, urlunparse
try:
import threading
@@ -22,7 +20,7 @@ except ImportError:
import dummy_threading as threading
-class MockRequest(object):
+class MockRequest:
"""Wraps a `requests.Request` to mimic a `urllib2.Request`.
The code in `cookielib.CookieJar` expects this interface in order to correctly
@@ -51,16 +49,22 @@ class MockRequest(object):
def get_full_url(self):
# Only return the response's URL if the user hadn't set the Host
# header
- if not self._r.headers.get('Host'):
+ if not self._r.headers.get("Host"):
return self._r.url
# If they did set it, retrieve it and reconstruct the expected domain
- host = to_native_string(self._r.headers['Host'], encoding='utf-8')
+ host = to_native_string(self._r.headers["Host"], encoding="utf-8")
parsed = urlparse(self._r.url)
# Reconstruct the URL as we expect it
- return urlunparse([
- parsed.scheme, host, parsed.path, parsed.params, parsed.query,
- parsed.fragment
- ])
+ return urlunparse(
+ [
+ parsed.scheme,
+ host,
+ parsed.path,
+ parsed.params,
+ parsed.query,
+ parsed.fragment,
+ ]
+ )
def is_unverifiable(self):
return True
@@ -73,7 +77,9 @@ class MockRequest(object):
def add_header(self, key, val):
"""cookielib has no legitimate use for this method; add it back if you find one."""
- raise NotImplementedError("Cookie headers should be added with add_unredirected_header()")
+ raise NotImplementedError(
+ "Cookie headers should be added with add_unredirected_header()"
+ )
def add_unredirected_header(self, name, value):
self._new_headers[name] = value
@@ -94,7 +100,7 @@ class MockRequest(object):
return self.get_host()
-class MockResponse(object):
+class MockResponse:
"""Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.
...what? Basically, expose the parsed HTTP headers from the server response
@@ -122,8 +128,7 @@ def extract_cookies_to_jar(jar, request, response):
:param request: our own requests.Request object
:param response: urllib3.HTTPResponse object
"""
- if not (hasattr(response, '_original_response') and
- response._original_response):
+ if not (hasattr(response, "_original_response") and response._original_response):
return
# the _original_response field is the wrapped httplib.HTTPResponse object,
req = MockRequest(request)
@@ -140,7 +145,7 @@ def get_cookie_header(jar, request):
"""
r = MockRequest(request)
jar.add_cookie_header(r)
- return r.get_new_headers().get('Cookie')
+ return r.get_new_headers().get("Cookie")
def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
@@ -205,7 +210,9 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
"""
# support client code that unsets cookies by assignment of a None value:
if value is None:
- remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path'))
+ remove_cookie_by_name(
+ self, name, domain=kwargs.get("domain"), path=kwargs.get("path")
+ )
return
if isinstance(value, Morsel):
@@ -305,16 +312,15 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
"""
dictionary = {}
for cookie in iter(self):
- if (
- (domain is None or cookie.domain == domain) and
- (path is None or cookie.path == path)
+ if (domain is None or cookie.domain == domain) and (
+ path is None or cookie.path == path
):
dictionary[cookie.name] = cookie.value
return dictionary
def __contains__(self, name):
try:
- return super(RequestsCookieJar, self).__contains__(name)
+ return super().__contains__(name)
except CookieConflictError:
return True
@@ -341,9 +347,13 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
remove_cookie_by_name(self, name)
def set_cookie(self, cookie, *args, **kwargs):
- if hasattr(cookie.value, 'startswith') and cookie.value.startswith('"') and cookie.value.endswith('"'):
- cookie.value = cookie.value.replace('\\"', '')
- return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs)
+ if (
+ hasattr(cookie.value, "startswith")
+ and cookie.value.startswith('"')
+ and cookie.value.endswith('"')
+ ):
+ cookie.value = cookie.value.replace('\\"', "")
+ return super().set_cookie(cookie, *args, **kwargs)
def update(self, other):
"""Updates this jar with cookies from another CookieJar or dict-like"""
@@ -351,7 +361,7 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
for cookie in other:
self.set_cookie(copy.copy(cookie))
else:
- super(RequestsCookieJar, self).update(other)
+ super().update(other)
def _find(self, name, domain=None, path=None):
"""Requests uses this method internally to get cookie values.
@@ -371,7 +381,7 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
if path is None or cookie.path == path:
return cookie.value
- raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
+ raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")
def _find_no_duplicates(self, name, domain=None, path=None):
"""Both ``__get_item__`` and ``get`` call this function: it's never
@@ -390,25 +400,29 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
if cookie.name == name:
if domain is None or cookie.domain == domain:
if path is None or cookie.path == path:
- if toReturn is not None: # if there are multiple cookies that meet passed in criteria
- raise CookieConflictError('There are multiple cookies with name, %r' % (name))
- toReturn = cookie.value # we will eventually return this as long as no cookie conflict
+ if toReturn is not None:
+ # if there are multiple cookies that meet passed in criteria
+ raise CookieConflictError(
+ f"There are multiple cookies with name, {name!r}"
+ )
+ # we will eventually return this as long as no cookie conflict
+ toReturn = cookie.value
if toReturn:
return toReturn
- raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
+ raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")
def __getstate__(self):
"""Unlike a normal CookieJar, this class is pickleable."""
state = self.__dict__.copy()
# remove the unpickleable RLock object
- state.pop('_cookies_lock')
+ state.pop("_cookies_lock")
return state
def __setstate__(self, state):
"""Unlike a normal CookieJar, this class is pickleable."""
self.__dict__.update(state)
- if '_cookies_lock' not in self.__dict__:
+ if "_cookies_lock" not in self.__dict__:
self._cookies_lock = threading.RLock()
def copy(self):
@@ -427,7 +441,7 @@ def _copy_cookie_jar(jar):
if jar is None:
return None
- if hasattr(jar, 'copy'):
+ if hasattr(jar, "copy"):
# We're dealing with an instance of RequestsCookieJar
return jar.copy()
# We're dealing with a generic CookieJar instance
@@ -445,31 +459,32 @@ def create_cookie(name, value, **kwargs):
and sent on every request (this is sometimes called a "supercookie").
"""
result = {
- 'version': 0,
- 'name': name,
- 'value': value,
- 'port': None,
- 'domain': '',
- 'path': '/',
- 'secure': False,
- 'expires': None,
- 'discard': True,
- 'comment': None,
- 'comment_url': None,
- 'rest': {'HttpOnly': None},
- 'rfc2109': False,
+ "version": 0,
+ "name": name,
+ "value": value,
+ "port": None,
+ "domain": "",
+ "path": "/",
+ "secure": False,
+ "expires": None,
+ "discard": True,
+ "comment": None,
+ "comment_url": None,
+ "rest": {"HttpOnly": None},
+ "rfc2109": False,
}
badargs = set(kwargs) - set(result)
if badargs:
- err = 'create_cookie() got unexpected keyword arguments: %s'
- raise TypeError(err % list(badargs))
+ raise TypeError(
+ f"create_cookie() got unexpected keyword arguments: {list(badargs)}"
+ )
result.update(kwargs)
- result['port_specified'] = bool(result['port'])
- result['domain_specified'] = bool(result['domain'])
- result['domain_initial_dot'] = result['domain'].startswith('.')
- result['path_specified'] = bool(result['path'])
+ result["port_specified"] = bool(result["port"])
+ result["domain_specified"] = bool(result["domain"])
+ result["domain_initial_dot"] = result["domain"].startswith(".")
+ result["path_specified"] = bool(result["path"])
return cookielib.Cookie(**result)
@@ -478,30 +493,28 @@ def morsel_to_cookie(morsel):
"""Convert a Morsel object into a Cookie containing the one k/v pair."""
expires = None
- if morsel['max-age']:
+ if morsel["max-age"]:
try:
- expires = int(time.time() + int(morsel['max-age']))
+ expires = int(time.time() + int(morsel["max-age"]))
except ValueError:
- raise TypeError('max-age: %s must be integer' % morsel['max-age'])
- elif morsel['expires']:
- time_template = '%a, %d-%b-%Y %H:%M:%S GMT'
- expires = calendar.timegm(
- time.strptime(morsel['expires'], time_template)
- )
+ raise TypeError(f"max-age: {morsel['max-age']} must be integer")
+ elif morsel["expires"]:
+ time_template = "%a, %d-%b-%Y %H:%M:%S GMT"
+ expires = calendar.timegm(time.strptime(morsel["expires"], time_template))
return create_cookie(
- comment=morsel['comment'],
- comment_url=bool(morsel['comment']),
+ comment=morsel["comment"],
+ comment_url=bool(morsel["comment"]),
discard=False,
- domain=morsel['domain'],
+ domain=morsel["domain"],
expires=expires,
name=morsel.key,
- path=morsel['path'],
+ path=morsel["path"],
port=None,
- rest={'HttpOnly': morsel['httponly']},
+ rest={"HttpOnly": morsel["httponly"]},
rfc2109=False,
- secure=bool(morsel['secure']),
+ secure=bool(morsel["secure"]),
value=morsel.value,
- version=morsel['version'] or 0,
+ version=morsel["version"] or 0,
)
@@ -534,11 +547,10 @@ def merge_cookies(cookiejar, cookies):
:rtype: CookieJar
"""
if not isinstance(cookiejar, cookielib.CookieJar):
- raise ValueError('You can only merge into CookieJar')
+ raise ValueError("You can only merge into CookieJar")
if isinstance(cookies, dict):
- cookiejar = cookiejar_from_dict(
- cookies, cookiejar=cookiejar, overwrite=False)
+ cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False)
elif isinstance(cookies, cookielib.CookieJar):
try:
cookiejar.update(cookies)
diff --git a/src/pip/_vendor/requests/exceptions.py b/src/pip/_vendor/requests/exceptions.py
index 9f0ad778b..168d07390 100644
--- a/src/pip/_vendor/requests/exceptions.py
+++ b/src/pip/_vendor/requests/exceptions.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
"""
requests.exceptions
~~~~~~~~~~~~~~~~~~~
@@ -8,6 +6,8 @@ This module contains the set of Requests' exceptions.
"""
from pip._vendor.urllib3.exceptions import HTTPError as BaseHTTPError
+from .compat import JSONDecodeError as CompatJSONDecodeError
+
class RequestException(IOError):
"""There was an ambiguous exception that occurred while handling your
@@ -16,19 +16,32 @@ class RequestException(IOError):
def __init__(self, *args, **kwargs):
"""Initialize RequestException with `request` and `response` objects."""
- response = kwargs.pop('response', None)
+ response = kwargs.pop("response", None)
self.response = response
- self.request = kwargs.pop('request', None)
- if (response is not None and not self.request and
- hasattr(response, 'request')):
+ self.request = kwargs.pop("request", None)
+ if response is not None and not self.request and hasattr(response, "request"):
self.request = self.response.request
- super(RequestException, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
class InvalidJSONError(RequestException):
"""A JSON error occurred."""
+class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError):
+ """Couldn't decode the text into json"""
+
+ def __init__(self, *args, **kwargs):
+ """
+ Construct the JSONDecodeError instance first with all
+ args. Then use it's args to construct the IOError so that
+ the json specific args aren't used as IOError specific args
+ and the error message from JSONDecodeError is preserved.
+ """
+ CompatJSONDecodeError.__init__(self, *args)
+ InvalidJSONError.__init__(self, *self.args, **kwargs)
+
+
class HTTPError(RequestException):
"""An HTTP error occurred."""
@@ -74,11 +87,11 @@ class TooManyRedirects(RequestException):
class MissingSchema(RequestException, ValueError):
- """The URL schema (e.g. http or https) is missing."""
+ """The URL scheme (e.g. http or https) is missing."""
class InvalidSchema(RequestException, ValueError):
- """See defaults.py for valid schemas."""
+ """The URL scheme provided is either invalid or unsupported."""
class InvalidURL(RequestException, ValueError):
@@ -112,6 +125,7 @@ class RetryError(RequestException):
class UnrewindableBodyError(RequestException):
"""Requests encountered an error when trying to rewind a body."""
+
# Warnings
diff --git a/src/pip/_vendor/requests/help.py b/src/pip/_vendor/requests/help.py
index 745f0d7b3..2d292c2f0 100644
--- a/src/pip/_vendor/requests/help.py
+++ b/src/pip/_vendor/requests/help.py
@@ -1,10 +1,9 @@
"""Module containing bug report helper(s)."""
-from __future__ import print_function
import json
import platform
-import sys
import ssl
+import sys
from pip._vendor import idna
from pip._vendor import urllib3
@@ -25,16 +24,16 @@ except ImportError:
OpenSSL = None
cryptography = None
else:
- import OpenSSL
import cryptography
+ import OpenSSL
def _implementation():
"""Return a dict with the Python implementation and version.
Provide both the name and the version of the Python implementation
- currently running. For example, on CPython 2.7.5 it will return
- {'name': 'CPython', 'version': '2.7.5'}.
+ currently running. For example, on CPython 3.10.3 it will return
+ {'name': 'CPython', 'version': '3.10.3'}.
This function works best on CPython and PyPy: in particular, it probably
doesn't work for Jython or IronPython. Future investigation should be done
@@ -42,83 +41,83 @@ def _implementation():
"""
implementation = platform.python_implementation()
- if implementation == 'CPython':
+ if implementation == "CPython":
implementation_version = platform.python_version()
- elif implementation == 'PyPy':
- implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major,
- sys.pypy_version_info.minor,
- sys.pypy_version_info.micro)
- if sys.pypy_version_info.releaselevel != 'final':
- implementation_version = ''.join([
- implementation_version, sys.pypy_version_info.releaselevel
- ])
- elif implementation == 'Jython':
+ elif implementation == "PyPy":
+ implementation_version = "{}.{}.{}".format(
+ sys.pypy_version_info.major,
+ sys.pypy_version_info.minor,
+ sys.pypy_version_info.micro,
+ )
+ if sys.pypy_version_info.releaselevel != "final":
+ implementation_version = "".join(
+ [implementation_version, sys.pypy_version_info.releaselevel]
+ )
+ elif implementation == "Jython":
implementation_version = platform.python_version() # Complete Guess
- elif implementation == 'IronPython':
+ elif implementation == "IronPython":
implementation_version = platform.python_version() # Complete Guess
else:
- implementation_version = 'Unknown'
+ implementation_version = "Unknown"
- return {'name': implementation, 'version': implementation_version}
+ return {"name": implementation, "version": implementation_version}
def info():
"""Generate information for a bug report."""
try:
platform_info = {
- 'system': platform.system(),
- 'release': platform.release(),
+ "system": platform.system(),
+ "release": platform.release(),
}
- except IOError:
+ except OSError:
platform_info = {
- 'system': 'Unknown',
- 'release': 'Unknown',
+ "system": "Unknown",
+ "release": "Unknown",
}
implementation_info = _implementation()
- urllib3_info = {'version': urllib3.__version__}
- charset_normalizer_info = {'version': None}
- chardet_info = {'version': None}
+ urllib3_info = {"version": urllib3.__version__}
+ charset_normalizer_info = {"version": None}
+ chardet_info = {"version": None}
if charset_normalizer:
- charset_normalizer_info = {'version': charset_normalizer.__version__}
+ charset_normalizer_info = {"version": charset_normalizer.__version__}
if chardet:
- chardet_info = {'version': chardet.__version__}
+ chardet_info = {"version": chardet.__version__}
pyopenssl_info = {
- 'version': None,
- 'openssl_version': '',
+ "version": None,
+ "openssl_version": "",
}
if OpenSSL:
pyopenssl_info = {
- 'version': OpenSSL.__version__,
- 'openssl_version': '%x' % OpenSSL.SSL.OPENSSL_VERSION_NUMBER,
+ "version": OpenSSL.__version__,
+ "openssl_version": f"{OpenSSL.SSL.OPENSSL_VERSION_NUMBER:x}",
}
cryptography_info = {
- 'version': getattr(cryptography, '__version__', ''),
+ "version": getattr(cryptography, "__version__", ""),
}
idna_info = {
- 'version': getattr(idna, '__version__', ''),
+ "version": getattr(idna, "__version__", ""),
}
system_ssl = ssl.OPENSSL_VERSION_NUMBER
- system_ssl_info = {
- 'version': '%x' % system_ssl if system_ssl is not None else ''
- }
+ system_ssl_info = {"version": f"{system_ssl:x}" if system_ssl is not None else ""}
return {
- 'platform': platform_info,
- 'implementation': implementation_info,
- 'system_ssl': system_ssl_info,
- 'using_pyopenssl': pyopenssl is not None,
- 'using_charset_normalizer': chardet is None,
- 'pyOpenSSL': pyopenssl_info,
- 'urllib3': urllib3_info,
- 'chardet': chardet_info,
- 'charset_normalizer': charset_normalizer_info,
- 'cryptography': cryptography_info,
- 'idna': idna_info,
- 'requests': {
- 'version': requests_version,
+ "platform": platform_info,
+ "implementation": implementation_info,
+ "system_ssl": system_ssl_info,
+ "using_pyopenssl": pyopenssl is not None,
+ "using_charset_normalizer": chardet is None,
+ "pyOpenSSL": pyopenssl_info,
+ "urllib3": urllib3_info,
+ "chardet": chardet_info,
+ "charset_normalizer": charset_normalizer_info,
+ "cryptography": cryptography_info,
+ "idna": idna_info,
+ "requests": {
+ "version": requests_version,
},
}
@@ -128,5 +127,5 @@ def main():
print(json.dumps(info(), sort_keys=True, indent=2))
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/src/pip/_vendor/requests/hooks.py b/src/pip/_vendor/requests/hooks.py
index 7a51f212c..d181ba2ec 100644
--- a/src/pip/_vendor/requests/hooks.py
+++ b/src/pip/_vendor/requests/hooks.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
"""
requests.hooks
~~~~~~~~~~~~~~
@@ -11,12 +9,13 @@ Available hooks:
``response``:
The response generated from a Request.
"""
-HOOKS = ['response']
+HOOKS = ["response"]
def default_hooks():
return {event: [] for event in HOOKS}
+
# TODO: response is the only one
@@ -25,7 +24,7 @@ def dispatch_hook(key, hooks, hook_data, **kwargs):
hooks = hooks or {}
hooks = hooks.get(key)
if hooks:
- if hasattr(hooks, '__call__'):
+ if hasattr(hooks, "__call__"):
hooks = [hooks]
for hook in hooks:
_hook_data = hook(hook_data, **kwargs)
diff --git a/src/pip/_vendor/requests/models.py b/src/pip/_vendor/requests/models.py
index c10c6011b..76e6f199c 100644
--- a/src/pip/_vendor/requests/models.py
+++ b/src/pip/_vendor/requests/models.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
"""
requests.models
~~~~~~~~~~~~~~~
@@ -8,46 +6,72 @@ This module contains the primary objects that power Requests.
"""
import datetime
-import sys
# Import encoding now, to avoid implicit import later.
# Implicit import within threads may cause LookupError when standard library is in a ZIP,
# such as in Embedded Python. See https://github.com/psf/requests/issues/3578.
-import encodings.idna
+import encodings.idna # noqa: F401
+from io import UnsupportedOperation
+from pip._vendor.urllib3.exceptions import (
+ DecodeError,
+ LocationParseError,
+ ProtocolError,
+ ReadTimeoutError,
+ SSLError,
+)
from pip._vendor.urllib3.fields import RequestField
from pip._vendor.urllib3.filepost import encode_multipart_formdata
from pip._vendor.urllib3.util import parse_url
-from pip._vendor.urllib3.exceptions import (
- DecodeError, ReadTimeoutError, ProtocolError, LocationParseError)
-
-from io import UnsupportedOperation
-from .hooks import default_hooks
-from .structures import CaseInsensitiveDict
-from .auth import HTTPBasicAuth
-from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar
-from .exceptions import (
- HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError,
- ContentDecodingError, ConnectionError, StreamConsumedError, InvalidJSONError)
from ._internal_utils import to_native_string, unicode_is_ascii
-from .utils import (
- guess_filename, get_auth_from_url, requote_uri,
- stream_decode_response_unicode, to_key_val_list, parse_header_links,
- iter_slices, guess_json_utf, super_len, check_header_validity)
+from .auth import HTTPBasicAuth
from .compat import (
- Callable, Mapping,
- cookielib, urlunparse, urlsplit, urlencode, str, bytes,
- is_py2, chardet, builtin_str, basestring)
+ Callable,
+ JSONDecodeError,
+ Mapping,
+ basestring,
+ builtin_str,
+ chardet,
+ cookielib,
+)
from .compat import json as complexjson
+from .compat import urlencode, urlsplit, urlunparse
+from .cookies import _copy_cookie_jar, cookiejar_from_dict, get_cookie_header
+from .exceptions import (
+ ChunkedEncodingError,
+ ConnectionError,
+ ContentDecodingError,
+ HTTPError,
+ InvalidJSONError,
+ InvalidURL,
+)
+from .exceptions import JSONDecodeError as RequestsJSONDecodeError
+from .exceptions import MissingSchema
+from .exceptions import SSLError as RequestsSSLError
+from .exceptions import StreamConsumedError
+from .hooks import default_hooks
from .status_codes import codes
+from .structures import CaseInsensitiveDict
+from .utils import (
+ check_header_validity,
+ get_auth_from_url,
+ guess_filename,
+ guess_json_utf,
+ iter_slices,
+ parse_header_links,
+ requote_uri,
+ stream_decode_response_unicode,
+ super_len,
+ to_key_val_list,
+)
#: The set of HTTP status codes that indicate an automatically
#: processable redirect.
REDIRECT_STATI = (
- codes.moved, # 301
- codes.found, # 302
- codes.other, # 303
+ codes.moved, # 301
+ codes.found, # 302
+ codes.other, # 303
codes.temporary_redirect, # 307
codes.permanent_redirect, # 308
)
@@ -57,7 +81,7 @@ CONTENT_CHUNK_SIZE = 10 * 1024
ITER_CHUNK_SIZE = 512
-class RequestEncodingMixin(object):
+class RequestEncodingMixin:
@property
def path_url(self):
"""Build the path URL to use."""
@@ -68,16 +92,16 @@ class RequestEncodingMixin(object):
path = p.path
if not path:
- path = '/'
+ path = "/"
url.append(path)
query = p.query
if query:
- url.append('?')
+ url.append("?")
url.append(query)
- return ''.join(url)
+ return "".join(url)
@staticmethod
def _encode_params(data):
@@ -90,18 +114,21 @@ class RequestEncodingMixin(object):
if isinstance(data, (str, bytes)):
return data
- elif hasattr(data, 'read'):
+ elif hasattr(data, "read"):
return data
- elif hasattr(data, '__iter__'):
+ elif hasattr(data, "__iter__"):
result = []
for k, vs in to_key_val_list(data):
- if isinstance(vs, basestring) or not hasattr(vs, '__iter__'):
+ if isinstance(vs, basestring) or not hasattr(vs, "__iter__"):
vs = [vs]
for v in vs:
if v is not None:
result.append(
- (k.encode('utf-8') if isinstance(k, str) else k,
- v.encode('utf-8') if isinstance(v, str) else v))
+ (
+ k.encode("utf-8") if isinstance(k, str) else k,
+ v.encode("utf-8") if isinstance(v, str) else v,
+ )
+ )
return urlencode(result, doseq=True)
else:
return data
@@ -116,7 +143,7 @@ class RequestEncodingMixin(object):
The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype)
or 4-tuples (filename, fileobj, contentype, custom_headers).
"""
- if (not files):
+ if not files:
raise ValueError("Files must be provided.")
elif isinstance(data, basestring):
raise ValueError("Data must not be a string.")
@@ -126,7 +153,7 @@ class RequestEncodingMixin(object):
files = to_key_val_list(files or {})
for field, val in fields:
- if isinstance(val, basestring) or not hasattr(val, '__iter__'):
+ if isinstance(val, basestring) or not hasattr(val, "__iter__"):
val = [val]
for v in val:
if v is not None:
@@ -135,8 +162,13 @@ class RequestEncodingMixin(object):
v = str(v)
new_fields.append(
- (field.decode('utf-8') if isinstance(field, bytes) else field,
- v.encode('utf-8') if isinstance(v, str) else v))
+ (
+ field.decode("utf-8")
+ if isinstance(field, bytes)
+ else field,
+ v.encode("utf-8") if isinstance(v, str) else v,
+ )
+ )
for (k, v) in files:
# support for explicit filename
@@ -155,7 +187,7 @@ class RequestEncodingMixin(object):
if isinstance(fp, (str, bytes, bytearray)):
fdata = fp
- elif hasattr(fp, 'read'):
+ elif hasattr(fp, "read"):
fdata = fp.read()
elif fp is None:
continue
@@ -171,16 +203,16 @@ class RequestEncodingMixin(object):
return body, content_type
-class RequestHooksMixin(object):
+class RequestHooksMixin:
def register_hook(self, event, hook):
"""Properly register a hook."""
if event not in self.hooks:
- raise ValueError('Unsupported event specified, with event name "%s"' % (event))
+ raise ValueError(f'Unsupported event specified, with event name "{event}"')
if isinstance(hook, Callable):
self.hooks[event].append(hook)
- elif hasattr(hook, '__iter__'):
+ elif hasattr(hook, "__iter__"):
self.hooks[event].extend(h for h in hook if isinstance(h, Callable))
def deregister_hook(self, event, hook):
@@ -223,9 +255,19 @@ class Request(RequestHooksMixin):
<PreparedRequest [GET]>
"""
- def __init__(self,
- method=None, url=None, headers=None, files=None, data=None,
- params=None, auth=None, cookies=None, hooks=None, json=None):
+ def __init__(
+ self,
+ method=None,
+ url=None,
+ headers=None,
+ files=None,
+ data=None,
+ params=None,
+ auth=None,
+ cookies=None,
+ hooks=None,
+ json=None,
+ ):
# Default empty dicts for dict params.
data = [] if data is None else data
@@ -249,7 +291,7 @@ class Request(RequestHooksMixin):
self.cookies = cookies
def __repr__(self):
- return '<Request [%s]>' % (self.method)
+ return f"<Request [{self.method}]>"
def prepare(self):
"""Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it."""
@@ -307,9 +349,19 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
#: integer denoting starting position of a readable file-like body.
self._body_position = None
- def prepare(self,
- method=None, url=None, headers=None, files=None, data=None,
- params=None, auth=None, cookies=None, hooks=None, json=None):
+ def prepare(
+ self,
+ method=None,
+ url=None,
+ headers=None,
+ files=None,
+ data=None,
+ params=None,
+ auth=None,
+ cookies=None,
+ hooks=None,
+ json=None,
+ ):
"""Prepares the entire request with the given parameters."""
self.prepare_method(method)
@@ -326,7 +378,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
self.prepare_hooks(hooks)
def __repr__(self):
- return '<PreparedRequest [%s]>' % (self.method)
+ return f"<PreparedRequest [{self.method}]>"
def copy(self):
p = PreparedRequest()
@@ -350,7 +402,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
from pip._vendor import idna
try:
- host = idna.encode(host, uts46=True).decode('utf-8')
+ host = idna.encode(host, uts46=True).decode("utf-8")
except idna.IDNAError:
raise UnicodeError
return host
@@ -363,9 +415,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
#: on python 3.x.
#: https://github.com/psf/requests/pull/2238
if isinstance(url, bytes):
- url = url.decode('utf8')
+ url = url.decode("utf8")
else:
- url = unicode(url) if is_py2 else str(url)
+ url = str(url)
# Remove leading whitespaces from url
url = url.lstrip()
@@ -373,7 +425,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
# Don't do any URL preparation for non-HTTP schemes like `mailto`,
# `data` etc to work around exceptions from `url_parse`, which
# handles RFC 3986 only.
- if ':' in url and not url.lower().startswith('http'):
+ if ":" in url and not url.lower().startswith("http"):
self.url = url
return
@@ -384,13 +436,13 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
raise InvalidURL(*e.args)
if not scheme:
- error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?")
- error = error.format(to_native_string(url, 'utf8'))
-
- raise MissingSchema(error)
+ raise MissingSchema(
+ f"Invalid URL {url!r}: No scheme supplied. "
+ f"Perhaps you meant https://{url}?"
+ )
if not host:
- raise InvalidURL("Invalid URL %r: No host supplied" % url)
+ raise InvalidURL(f"Invalid URL {url!r}: No host supplied")
# In general, we want to try IDNA encoding the hostname if the string contains
# non-ASCII characters. This allows users to automatically get the correct IDNA
@@ -400,33 +452,21 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
try:
host = self._get_idna_encoded_host(host)
except UnicodeError:
- raise InvalidURL('URL has an invalid label.')
- elif host.startswith(u'*'):
- raise InvalidURL('URL has an invalid label.')
+ raise InvalidURL("URL has an invalid label.")
+ elif host.startswith(("*", ".")):
+ raise InvalidURL("URL has an invalid label.")
# Carefully reconstruct the network location
- netloc = auth or ''
+ netloc = auth or ""
if netloc:
- netloc += '@'
+ netloc += "@"
netloc += host
if port:
- netloc += ':' + str(port)
+ netloc += f":{port}"
# Bare domains aren't valid URLs.
if not path:
- path = '/'
-
- if is_py2:
- if isinstance(scheme, str):
- scheme = scheme.encode('utf-8')
- if isinstance(netloc, str):
- netloc = netloc.encode('utf-8')
- if isinstance(path, str):
- path = path.encode('utf-8')
- if isinstance(query, str):
- query = query.encode('utf-8')
- if isinstance(fragment, str):
- fragment = fragment.encode('utf-8')
+ path = "/"
if isinstance(params, (str, bytes)):
params = to_native_string(params)
@@ -434,7 +474,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
enc_params = self._encode_params(params)
if enc_params:
if query:
- query = '%s&%s' % (query, enc_params)
+ query = f"{query}&{enc_params}"
else:
query = enc_params
@@ -465,20 +505,22 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if not data and json is not None:
# urllib3 requires a bytes-like body. Python 2's json.dumps
# provides this natively, but Python 3 gives a Unicode string.
- content_type = 'application/json'
+ content_type = "application/json"
try:
- body = complexjson.dumps(json, allow_nan=False)
+ body = complexjson.dumps(json, allow_nan=False)
except ValueError as ve:
- raise InvalidJSONError(ve, request=self)
+ raise InvalidJSONError(ve, request=self)
if not isinstance(body, bytes):
- body = body.encode('utf-8')
+ body = body.encode("utf-8")
- is_stream = all([
- hasattr(data, '__iter__'),
- not isinstance(data, (basestring, list, tuple, Mapping))
- ])
+ is_stream = all(
+ [
+ hasattr(data, "__iter__"),
+ not isinstance(data, (basestring, list, tuple, Mapping)),
+ ]
+ )
if is_stream:
try:
@@ -488,24 +530,26 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
body = data
- if getattr(body, 'tell', None) is not None:
+ if getattr(body, "tell", None) is not None:
# Record the current file position before reading.
# This will allow us to rewind a file in the event
# of a redirect.
try:
self._body_position = body.tell()
- except (IOError, OSError):
+ except OSError:
# This differentiates from None, allowing us to catch
# a failed `tell()` later when trying to rewind the body
self._body_position = object()
if files:
- raise NotImplementedError('Streamed bodies and files are mutually exclusive.')
+ raise NotImplementedError(
+ "Streamed bodies and files are mutually exclusive."
+ )
if length:
- self.headers['Content-Length'] = builtin_str(length)
+ self.headers["Content-Length"] = builtin_str(length)
else:
- self.headers['Transfer-Encoding'] = 'chunked'
+ self.headers["Transfer-Encoding"] = "chunked"
else:
# Multi-part file uploads.
if files:
@@ -513,16 +557,16 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
else:
if data:
body = self._encode_params(data)
- if isinstance(data, basestring) or hasattr(data, 'read'):
+ if isinstance(data, basestring) or hasattr(data, "read"):
content_type = None
else:
- content_type = 'application/x-www-form-urlencoded'
+ content_type = "application/x-www-form-urlencoded"
self.prepare_content_length(body)
# Add content-type if it wasn't explicitly provided.
- if content_type and ('content-type' not in self.headers):
- self.headers['Content-Type'] = content_type
+ if content_type and ("content-type" not in self.headers):
+ self.headers["Content-Type"] = content_type
self.body = body
@@ -533,13 +577,16 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if length:
# If length exists, set it. Otherwise, we fallback
# to Transfer-Encoding: chunked.
- self.headers['Content-Length'] = builtin_str(length)
- elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None:
+ self.headers["Content-Length"] = builtin_str(length)
+ elif (
+ self.method not in ("GET", "HEAD")
+ and self.headers.get("Content-Length") is None
+ ):
# Set Content-Length to 0 for methods that can have a body
# but don't provide one. (i.e. not GET or HEAD)
- self.headers['Content-Length'] = '0'
+ self.headers["Content-Length"] = "0"
- def prepare_auth(self, auth, url=''):
+ def prepare_auth(self, auth, url=""):
"""Prepares the given HTTP auth data."""
# If no Auth is explicitly provided, extract it from the URL first.
@@ -579,7 +626,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
cookie_header = get_cookie_header(self._cookies, self)
if cookie_header is not None:
- self.headers['Cookie'] = cookie_header
+ self.headers["Cookie"] = cookie_header
def prepare_hooks(self, hooks):
"""Prepares the given hooks."""
@@ -591,14 +638,22 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
self.register_hook(event, hooks[event])
-class Response(object):
+class Response:
"""The :class:`Response <Response>` object, which contains a
server's response to an HTTP request.
"""
__attrs__ = [
- '_content', 'status_code', 'headers', 'url', 'history',
- 'encoding', 'reason', 'cookies', 'elapsed', 'request'
+ "_content",
+ "status_code",
+ "headers",
+ "url",
+ "history",
+ "encoding",
+ "reason",
+ "cookies",
+ "elapsed",
+ "request",
]
def __init__(self):
@@ -667,11 +722,11 @@ class Response(object):
setattr(self, name, value)
# pickled objects do not have .raw
- setattr(self, '_content_consumed', True)
- setattr(self, 'raw', None)
+ setattr(self, "_content_consumed", True)
+ setattr(self, "raw", None)
def __repr__(self):
- return '<Response [%s]>' % (self.status_code)
+ return f"<Response [{self.status_code}]>"
def __bool__(self):
"""Returns True if :attr:`status_code` is less than 400.
@@ -717,12 +772,15 @@ class Response(object):
"""True if this Response is a well-formed HTTP redirect that could have
been processed automatically (by :meth:`Session.resolve_redirects`).
"""
- return ('location' in self.headers and self.status_code in REDIRECT_STATI)
+ return "location" in self.headers and self.status_code in REDIRECT_STATI
@property
def is_permanent_redirect(self):
"""True if this Response one of the permanent versions of redirect."""
- return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect))
+ return "location" in self.headers and self.status_code in (
+ codes.moved_permanently,
+ codes.permanent_redirect,
+ )
@property
def next(self):
@@ -732,7 +790,7 @@ class Response(object):
@property
def apparent_encoding(self):
"""The apparent encoding, provided by the charset_normalizer or chardet libraries."""
- return chardet.detect(self.content)['encoding']
+ return chardet.detect(self.content)["encoding"]
def iter_content(self, chunk_size=1, decode_unicode=False):
"""Iterates over the response data. When stream=True is set on the
@@ -753,16 +811,17 @@ class Response(object):
def generate():
# Special case for urllib3.
- if hasattr(self.raw, 'stream'):
+ if hasattr(self.raw, "stream"):
try:
- for chunk in self.raw.stream(chunk_size, decode_content=True):
- yield chunk
+ yield from self.raw.stream(chunk_size, decode_content=True)
except ProtocolError as e:
raise ChunkedEncodingError(e)
except DecodeError as e:
raise ContentDecodingError(e)
except ReadTimeoutError as e:
raise ConnectionError(e)
+ except SSLError as e:
+ raise RequestsSSLError(e)
else:
# Standard file-like object.
while True:
@@ -776,7 +835,9 @@ class Response(object):
if self._content_consumed and isinstance(self._content, bool):
raise StreamConsumedError()
elif chunk_size is not None and not isinstance(chunk_size, int):
- raise TypeError("chunk_size must be an int, it is instead a %s." % type(chunk_size))
+ raise TypeError(
+ f"chunk_size must be an int, it is instead a {type(chunk_size)}."
+ )
# simulate reading small chunks of the content
reused_chunks = iter_slices(self._content, chunk_size)
@@ -789,7 +850,9 @@ class Response(object):
return chunks
- def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None):
+ def iter_lines(
+ self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None
+ ):
"""Iterates over the response data, one line at a time. When
stream=True is set on the request, this avoids reading the
content at once into memory for large responses.
@@ -799,7 +862,9 @@ class Response(object):
pending = None
- for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode):
+ for chunk in self.iter_content(
+ chunk_size=chunk_size, decode_unicode=decode_unicode
+ ):
if pending is not None:
chunk = pending + chunk
@@ -814,8 +879,7 @@ class Response(object):
else:
pending = None
- for line in lines:
- yield line
+ yield from lines
if pending is not None:
yield pending
@@ -827,13 +891,12 @@ class Response(object):
if self._content is False:
# Read the contents.
if self._content_consumed:
- raise RuntimeError(
- 'The content for this response was already consumed')
+ raise RuntimeError("The content for this response was already consumed")
if self.status_code == 0 or self.raw is None:
self._content = None
else:
- self._content = b''.join(self.iter_content(CONTENT_CHUNK_SIZE)) or b''
+ self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
self._content_consumed = True
# don't need to release the connection; that's been handled by urllib3
@@ -858,7 +921,7 @@ class Response(object):
encoding = self.encoding
if not self.content:
- return str('')
+ return ""
# Fallback to auto-detected encoding.
if self.encoding is None:
@@ -866,7 +929,7 @@ class Response(object):
# Decode unicode from given encoding.
try:
- content = str(self.content, encoding, errors='replace')
+ content = str(self.content, encoding, errors="replace")
except (LookupError, TypeError):
# A LookupError is raised if the encoding was not found which could
# indicate a misspelling or similar mistake.
@@ -874,7 +937,7 @@ class Response(object):
# A TypeError can be raised if encoding is None
#
# So we try blindly encoding.
- content = str(self.content, errors='replace')
+ content = str(self.content, errors="replace")
return content
@@ -882,12 +945,8 @@ class Response(object):
r"""Returns the json-encoded content of a response, if any.
:param \*\*kwargs: Optional arguments that ``json.loads`` takes.
- :raises simplejson.JSONDecodeError: If the response body does not
- contain valid json and simplejson is installed.
- :raises json.JSONDecodeError: If the response body does not contain
- valid json and simplejson is not installed on Python 3.
- :raises ValueError: If the response body does not contain valid
- json and simplejson is not installed on Python 2.
+ :raises requests.exceptions.JSONDecodeError: If the response body does not
+ contain valid json.
"""
if not self.encoding and self.content and len(self.content) > 3:
@@ -898,56 +957,65 @@ class Response(object):
encoding = guess_json_utf(self.content)
if encoding is not None:
try:
- return complexjson.loads(
- self.content.decode(encoding), **kwargs
- )
+ return complexjson.loads(self.content.decode(encoding), **kwargs)
except UnicodeDecodeError:
# Wrong UTF codec detected; usually because it's not UTF-8
# but some other 8-bit codec. This is an RFC violation,
# and the server didn't bother to tell us what codec *was*
# used.
pass
- return complexjson.loads(self.text, **kwargs)
+ except JSONDecodeError as e:
+ raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
+
+ try:
+ return complexjson.loads(self.text, **kwargs)
+ except JSONDecodeError as e:
+ # Catch JSON-related errors and raise as requests.JSONDecodeError
+ # This aliases json.JSONDecodeError and simplejson.JSONDecodeError
+ raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
@property
def links(self):
"""Returns the parsed header links of the response, if any."""
- header = self.headers.get('link')
+ header = self.headers.get("link")
- # l = MultiDict()
- l = {}
+ resolved_links = {}
if header:
links = parse_header_links(header)
for link in links:
- key = link.get('rel') or link.get('url')
- l[key] = link
+ key = link.get("rel") or link.get("url")
+ resolved_links[key] = link
- return l
+ return resolved_links
def raise_for_status(self):
"""Raises :class:`HTTPError`, if one occurred."""
- http_error_msg = ''
+ http_error_msg = ""
if isinstance(self.reason, bytes):
# We attempt to decode utf-8 first because some servers
# choose to localize their reason strings. If the string
# isn't utf-8, we fall back to iso-8859-1 for all other
# encodings. (See PR #3538)
try:
- reason = self.reason.decode('utf-8')
+ reason = self.reason.decode("utf-8")
except UnicodeDecodeError:
- reason = self.reason.decode('iso-8859-1')
+ reason = self.reason.decode("iso-8859-1")
else:
reason = self.reason
if 400 <= self.status_code < 500:
- http_error_msg = u'%s Client Error: %s for url: %s' % (self.status_code, reason, self.url)
+ http_error_msg = (
+ f"{self.status_code} Client Error: {reason} for url: {self.url}"
+ )
elif 500 <= self.status_code < 600:
- http_error_msg = u'%s Server Error: %s for url: %s' % (self.status_code, reason, self.url)
+ http_error_msg = (
+ f"{self.status_code} Server Error: {reason} for url: {self.url}"
+ )
if http_error_msg:
raise HTTPError(http_error_msg, response=self)
@@ -961,6 +1029,6 @@ class Response(object):
if not self._content_consumed:
self.raw.close()
- release_conn = getattr(self.raw, 'release_conn', None)
+ release_conn = getattr(self.raw, "release_conn", None)
if release_conn is not None:
release_conn()
diff --git a/src/pip/_vendor/requests/sessions.py b/src/pip/_vendor/requests/sessions.py
index ae4bcc8e7..6cb3b4dae 100644
--- a/src/pip/_vendor/requests/sessions.py
+++ b/src/pip/_vendor/requests/sessions.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
"""
requests.sessions
~~~~~~~~~~~~~~~~~
@@ -10,39 +8,52 @@ requests (cookies, auth, proxies).
import os
import sys
import time
-from datetime import timedelta
from collections import OrderedDict
+from datetime import timedelta
+from ._internal_utils import to_native_string
+from .adapters import HTTPAdapter
from .auth import _basic_auth_str
-from .compat import cookielib, is_py3, urljoin, urlparse, Mapping
+from .compat import Mapping, cookielib, urljoin, urlparse
from .cookies import (
- cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
-from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT
-from .hooks import default_hooks, dispatch_hook
-from ._internal_utils import to_native_string
-from .utils import to_key_val_list, default_headers, DEFAULT_PORTS
+ RequestsCookieJar,
+ cookiejar_from_dict,
+ extract_cookies_to_jar,
+ merge_cookies,
+)
from .exceptions import (
- TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError)
-
-from .structures import CaseInsensitiveDict
-from .adapters import HTTPAdapter
-
-from .utils import (
- requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies,
- get_auth_from_url, rewind_body
+ ChunkedEncodingError,
+ ContentDecodingError,
+ InvalidSchema,
+ TooManyRedirects,
)
-
-from .status_codes import codes
+from .hooks import default_hooks, dispatch_hook
# formerly defined here, reexposed here for backward compatibility
-from .models import REDIRECT_STATI
+from .models import ( # noqa: F401
+ DEFAULT_REDIRECT_LIMIT,
+ REDIRECT_STATI,
+ PreparedRequest,
+ Request,
+)
+from .status_codes import codes
+from .structures import CaseInsensitiveDict
+from .utils import ( # noqa: F401
+ DEFAULT_PORTS,
+ default_headers,
+ get_auth_from_url,
+ get_environ_proxies,
+ get_netrc_auth,
+ requote_uri,
+ resolve_proxies,
+ rewind_body,
+ should_bypass_proxies,
+ to_key_val_list,
+)
# Preferred clock, based on which one is more accurate on a given system.
-if sys.platform == 'win32':
- try: # Python 3.4+
- preferred_clock = time.perf_counter
- except AttributeError: # Earlier than Python 3.
- preferred_clock = time.clock
+if sys.platform == "win32":
+ preferred_clock = time.perf_counter
else:
preferred_clock = time.time
@@ -61,8 +72,7 @@ def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
# Bypass if not a dictionary (e.g. verify)
if not (
- isinstance(session_setting, Mapping) and
- isinstance(request_setting, Mapping)
+ isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping)
):
return request_setting
@@ -84,17 +94,16 @@ def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
This is necessary because when request_hooks == {'response': []}, the
merge breaks Session hooks entirely.
"""
- if session_hooks is None or session_hooks.get('response') == []:
+ if session_hooks is None or session_hooks.get("response") == []:
return request_hooks
- if request_hooks is None or request_hooks.get('response') == []:
+ if request_hooks is None or request_hooks.get("response") == []:
return session_hooks
return merge_setting(request_hooks, session_hooks, dict_class)
-class SessionRedirectMixin(object):
-
+class SessionRedirectMixin:
def get_redirect_target(self, resp):
"""Receives a Response. Returns a redirect URI or ``None``"""
# Due to the nature of how requests processes redirects this method will
@@ -104,16 +113,15 @@ class SessionRedirectMixin(object):
# to cache the redirect location onto the response object as a private
# attribute.
if resp.is_redirect:
- location = resp.headers['location']
+ location = resp.headers["location"]
# Currently the underlying http module on py3 decode headers
# in latin1, but empirical evidence suggests that latin1 is very
# rarely used with non-ASCII characters in HTTP headers.
# It is more likely to get UTF8 header rather than latin1.
# This causes incorrect handling of UTF8 encoded location headers.
# To solve this, we re-encode the location in latin1.
- if is_py3:
- location = location.encode('latin1')
- return to_native_string(location, 'utf8')
+ location = location.encode("latin1")
+ return to_native_string(location, "utf8")
return None
def should_strip_auth(self, old_url, new_url):
@@ -126,23 +134,40 @@ class SessionRedirectMixin(object):
# ports. This isn't specified by RFC 7235, but is kept to avoid
# breaking backwards compatibility with older versions of requests
# that allowed any redirects on the same host.
- if (old_parsed.scheme == 'http' and old_parsed.port in (80, None)
- and new_parsed.scheme == 'https' and new_parsed.port in (443, None)):
+ if (
+ old_parsed.scheme == "http"
+ and old_parsed.port in (80, None)
+ and new_parsed.scheme == "https"
+ and new_parsed.port in (443, None)
+ ):
return False
# Handle default port usage corresponding to scheme.
changed_port = old_parsed.port != new_parsed.port
changed_scheme = old_parsed.scheme != new_parsed.scheme
default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None)
- if (not changed_scheme and old_parsed.port in default_port
- and new_parsed.port in default_port):
+ if (
+ not changed_scheme
+ and old_parsed.port in default_port
+ and new_parsed.port in default_port
+ ):
return False
# Standard case: root URI must match
return changed_port or changed_scheme
- def resolve_redirects(self, resp, req, stream=False, timeout=None,
- verify=True, cert=None, proxies=None, yield_requests=False, **adapter_kwargs):
+ def resolve_redirects(
+ self,
+ resp,
+ req,
+ stream=False,
+ timeout=None,
+ verify=True,
+ cert=None,
+ proxies=None,
+ yield_requests=False,
+ **adapter_kwargs,
+ ):
"""Receives a Response. Returns a generator of Responses or Requests."""
hist = [] # keep track of history
@@ -163,19 +188,21 @@ class SessionRedirectMixin(object):
resp.raw.read(decode_content=False)
if len(resp.history) >= self.max_redirects:
- raise TooManyRedirects('Exceeded {} redirects.'.format(self.max_redirects), response=resp)
+ raise TooManyRedirects(
+ f"Exceeded {self.max_redirects} redirects.", response=resp
+ )
# Release the connection back into the pool.
resp.close()
# Handle redirection without scheme (see: RFC 1808 Section 4)
- if url.startswith('//'):
+ if url.startswith("//"):
parsed_rurl = urlparse(resp.url)
- url = ':'.join([to_native_string(parsed_rurl.scheme), url])
+ url = ":".join([to_native_string(parsed_rurl.scheme), url])
# Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)
parsed = urlparse(url)
- if parsed.fragment == '' and previous_fragment:
+ if parsed.fragment == "" and previous_fragment:
parsed = parsed._replace(fragment=previous_fragment)
elif parsed.fragment:
previous_fragment = parsed.fragment
@@ -194,15 +221,18 @@ class SessionRedirectMixin(object):
self.rebuild_method(prepared_request, resp)
# https://github.com/psf/requests/issues/1084
- if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect):
+ if resp.status_code not in (
+ codes.temporary_redirect,
+ codes.permanent_redirect,
+ ):
# https://github.com/psf/requests/issues/3490
- purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding')
+ purged_headers = ("Content-Length", "Content-Type", "Transfer-Encoding")
for header in purged_headers:
prepared_request.headers.pop(header, None)
prepared_request.body = None
headers = prepared_request.headers
- headers.pop('Cookie', None)
+ headers.pop("Cookie", None)
# Extract any cookies sent on the response to the cookiejar
# in the new request. Because we've mutated our copied prepared
@@ -218,9 +248,8 @@ class SessionRedirectMixin(object):
# A failed tell() sets `_body_position` to `object()`. This non-None
# value ensures `rewindable` will be True, allowing us to raise an
# UnrewindableBodyError, instead of hanging the connection.
- rewindable = (
- prepared_request._body_position is not None and
- ('Content-Length' in headers or 'Transfer-Encoding' in headers)
+ rewindable = prepared_request._body_position is not None and (
+ "Content-Length" in headers or "Transfer-Encoding" in headers
)
# Attempt to rewind consumed file-like object.
@@ -242,7 +271,7 @@ class SessionRedirectMixin(object):
cert=cert,
proxies=proxies,
allow_redirects=False,
- **adapter_kwargs
+ **adapter_kwargs,
)
extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
@@ -259,17 +288,18 @@ class SessionRedirectMixin(object):
headers = prepared_request.headers
url = prepared_request.url
- if 'Authorization' in headers and self.should_strip_auth(response.request.url, url):
+ if "Authorization" in headers and self.should_strip_auth(
+ response.request.url, url
+ ):
# If we get redirected to a new host, we should strip out any
# authentication headers.
- del headers['Authorization']
+ del headers["Authorization"]
# .netrc might have more auth for us on our new host.
new_auth = get_netrc_auth(url) if self.trust_env else None
if new_auth is not None:
prepared_request.prepare_auth(new_auth)
-
def rebuild_proxies(self, prepared_request, proxies):
"""This method re-evaluates the proxy configuration by considering the
environment variables. If we are redirected to a URL covered by
@@ -282,24 +312,12 @@ class SessionRedirectMixin(object):
:rtype: dict
"""
- proxies = proxies if proxies is not None else {}
headers = prepared_request.headers
- url = prepared_request.url
- scheme = urlparse(url).scheme
- new_proxies = proxies.copy()
- no_proxy = proxies.get('no_proxy')
+ scheme = urlparse(prepared_request.url).scheme
+ new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env)
- bypass_proxy = should_bypass_proxies(url, no_proxy=no_proxy)
- if self.trust_env and not bypass_proxy:
- environ_proxies = get_environ_proxies(url, no_proxy=no_proxy)
-
- proxy = environ_proxies.get(scheme, environ_proxies.get('all'))
-
- if proxy:
- new_proxies.setdefault(scheme, proxy)
-
- if 'Proxy-Authorization' in headers:
- del headers['Proxy-Authorization']
+ if "Proxy-Authorization" in headers:
+ del headers["Proxy-Authorization"]
try:
username, password = get_auth_from_url(new_proxies[scheme])
@@ -307,7 +325,7 @@ class SessionRedirectMixin(object):
username, password = None, None
if username and password:
- headers['Proxy-Authorization'] = _basic_auth_str(username, password)
+ headers["Proxy-Authorization"] = _basic_auth_str(username, password)
return new_proxies
@@ -318,18 +336,18 @@ class SessionRedirectMixin(object):
method = prepared_request.method
# https://tools.ietf.org/html/rfc7231#section-6.4.4
- if response.status_code == codes.see_other and method != 'HEAD':
- method = 'GET'
+ if response.status_code == codes.see_other and method != "HEAD":
+ method = "GET"
# Do what the browsers do, despite standards...
# First, turn 302s into GETs.
- if response.status_code == codes.found and method != 'HEAD':
- method = 'GET'
+ if response.status_code == codes.found and method != "HEAD":
+ method = "GET"
# Second, if a POST is responded to with a 301, turn it into a GET.
# This bizarre behaviour is explained in Issue 1704.
- if response.status_code == codes.moved and method == 'POST':
- method = 'GET'
+ if response.status_code == codes.moved and method == "POST":
+ method = "GET"
prepared_request.method = method
@@ -354,9 +372,18 @@ class Session(SessionRedirectMixin):
"""
__attrs__ = [
- 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify',
- 'cert', 'adapters', 'stream', 'trust_env',
- 'max_redirects',
+ "headers",
+ "cookies",
+ "auth",
+ "proxies",
+ "hooks",
+ "params",
+ "verify",
+ "cert",
+ "adapters",
+ "stream",
+ "trust_env",
+ "max_redirects",
]
def __init__(self):
@@ -418,8 +445,8 @@ class Session(SessionRedirectMixin):
# Default connection adapters.
self.adapters = OrderedDict()
- self.mount('https://', HTTPAdapter())
- self.mount('http://', HTTPAdapter())
+ self.mount("https://", HTTPAdapter())
+ self.mount("http://", HTTPAdapter())
def __enter__(self):
return self
@@ -445,7 +472,8 @@ class Session(SessionRedirectMixin):
# Merge with session cookies
merged_cookies = merge_cookies(
- merge_cookies(RequestsCookieJar(), self.cookies), cookies)
+ merge_cookies(RequestsCookieJar(), self.cookies), cookies
+ )
# Set environment's basic authentication if not explicitly set.
auth = request.auth
@@ -459,7 +487,9 @@ class Session(SessionRedirectMixin):
files=request.files,
data=request.data,
json=request.json,
- headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict),
+ headers=merge_setting(
+ request.headers, self.headers, dict_class=CaseInsensitiveDict
+ ),
params=merge_setting(request.params, self.params),
auth=merge_setting(auth, self.auth),
cookies=merged_cookies,
@@ -467,10 +497,25 @@ class Session(SessionRedirectMixin):
)
return p
- def request(self, method, url,
- params=None, data=None, headers=None, cookies=None, files=None,
- auth=None, timeout=None, allow_redirects=True, proxies=None,
- hooks=None, stream=None, verify=None, cert=None, json=None):
+ def request(
+ self,
+ method,
+ url,
+ params=None,
+ data=None,
+ headers=None,
+ cookies=None,
+ files=None,
+ auth=None,
+ timeout=None,
+ allow_redirects=True,
+ proxies=None,
+ hooks=None,
+ stream=None,
+ verify=None,
+ cert=None,
+ json=None,
+ ):
"""Constructs a :class:`Request <Request>`, prepares it and sends it.
Returns :class:`Response <Response>` object.
@@ -506,7 +551,7 @@ class Session(SessionRedirectMixin):
``False``, requests will accept any TLS certificate presented by
the server, and will ignore hostname mismatches and/or expired
certificates, which will make your application vulnerable to
- man-in-the-middle (MitM) attacks. Setting verify to ``False``
+ man-in-the-middle (MitM) attacks. Setting verify to ``False``
may be useful during local development or testing.
:param cert: (optional) if String, path to ssl client cert file (.pem).
If Tuple, ('cert', 'key') pair.
@@ -535,8 +580,8 @@ class Session(SessionRedirectMixin):
# Send the request.
send_kwargs = {
- 'timeout': timeout,
- 'allow_redirects': allow_redirects,
+ "timeout": timeout,
+ "allow_redirects": allow_redirects,
}
send_kwargs.update(settings)
resp = self.send(prep, **send_kwargs)
@@ -551,8 +596,8 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response
"""
- kwargs.setdefault('allow_redirects', True)
- return self.request('GET', url, **kwargs)
+ kwargs.setdefault("allow_redirects", True)
+ return self.request("GET", url, **kwargs)
def options(self, url, **kwargs):
r"""Sends a OPTIONS request. Returns :class:`Response` object.
@@ -562,8 +607,8 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response
"""
- kwargs.setdefault('allow_redirects', True)
- return self.request('OPTIONS', url, **kwargs)
+ kwargs.setdefault("allow_redirects", True)
+ return self.request("OPTIONS", url, **kwargs)
def head(self, url, **kwargs):
r"""Sends a HEAD request. Returns :class:`Response` object.
@@ -573,8 +618,8 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response
"""
- kwargs.setdefault('allow_redirects', False)
- return self.request('HEAD', url, **kwargs)
+ kwargs.setdefault("allow_redirects", False)
+ return self.request("HEAD", url, **kwargs)
def post(self, url, data=None, json=None, **kwargs):
r"""Sends a POST request. Returns :class:`Response` object.
@@ -587,7 +632,7 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response
"""
- return self.request('POST', url, data=data, json=json, **kwargs)
+ return self.request("POST", url, data=data, json=json, **kwargs)
def put(self, url, data=None, **kwargs):
r"""Sends a PUT request. Returns :class:`Response` object.
@@ -599,7 +644,7 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response
"""
- return self.request('PUT', url, data=data, **kwargs)
+ return self.request("PUT", url, data=data, **kwargs)
def patch(self, url, data=None, **kwargs):
r"""Sends a PATCH request. Returns :class:`Response` object.
@@ -611,7 +656,7 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response
"""
- return self.request('PATCH', url, data=data, **kwargs)
+ return self.request("PATCH", url, data=data, **kwargs)
def delete(self, url, **kwargs):
r"""Sends a DELETE request. Returns :class:`Response` object.
@@ -621,7 +666,7 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response
"""
- return self.request('DELETE', url, **kwargs)
+ return self.request("DELETE", url, **kwargs)
def send(self, request, **kwargs):
"""Send a given PreparedRequest.
@@ -630,19 +675,20 @@ class Session(SessionRedirectMixin):
"""
# Set defaults that the hooks can utilize to ensure they always have
# the correct parameters to reproduce the previous request.
- kwargs.setdefault('stream', self.stream)
- kwargs.setdefault('verify', self.verify)
- kwargs.setdefault('cert', self.cert)
- kwargs.setdefault('proxies', self.rebuild_proxies(request, self.proxies))
+ kwargs.setdefault("stream", self.stream)
+ kwargs.setdefault("verify", self.verify)
+ kwargs.setdefault("cert", self.cert)
+ if "proxies" not in kwargs:
+ kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env)
# It's possible that users might accidentally send a Request object.
# Guard against that specific failure case.
if isinstance(request, Request):
- raise ValueError('You can only send PreparedRequests.')
+ raise ValueError("You can only send PreparedRequests.")
# Set up variables needed for resolve_redirects and dispatching of hooks
- allow_redirects = kwargs.pop('allow_redirects', True)
- stream = kwargs.get('stream')
+ allow_redirects = kwargs.pop("allow_redirects", True)
+ stream = kwargs.get("stream")
hooks = request.hooks
# Get the appropriate adapter to use
@@ -659,7 +705,7 @@ class Session(SessionRedirectMixin):
r.elapsed = timedelta(seconds=elapsed)
# Response manipulation hooks
- r = dispatch_hook('response', hooks, r, **kwargs)
+ r = dispatch_hook("response", hooks, r, **kwargs)
# Persist cookies
if r.history:
@@ -689,7 +735,9 @@ class Session(SessionRedirectMixin):
# If redirects aren't being followed, store the response on the Request for Response.next().
if not allow_redirects:
try:
- r._next = next(self.resolve_redirects(r, request, yield_requests=True, **kwargs))
+ r._next = next(
+ self.resolve_redirects(r, request, yield_requests=True, **kwargs)
+ )
except StopIteration:
pass
@@ -707,16 +755,19 @@ class Session(SessionRedirectMixin):
# Gather clues from the surrounding environment.
if self.trust_env:
# Set environment's proxies.
- no_proxy = proxies.get('no_proxy') if proxies is not None else None
+ no_proxy = proxies.get("no_proxy") if proxies is not None else None
env_proxies = get_environ_proxies(url, no_proxy=no_proxy)
for (k, v) in env_proxies.items():
proxies.setdefault(k, v)
- # Look for requests environment configuration and be compatible
- # with cURL.
+ # Look for requests environment configuration
+ # and be compatible with cURL.
if verify is True or verify is None:
- verify = (os.environ.get('REQUESTS_CA_BUNDLE') or
- os.environ.get('CURL_CA_BUNDLE'))
+ verify = (
+ os.environ.get("REQUESTS_CA_BUNDLE")
+ or os.environ.get("CURL_CA_BUNDLE")
+ or verify
+ )
# Merge all the kwargs.
proxies = merge_setting(proxies, self.proxies)
@@ -724,8 +775,7 @@ class Session(SessionRedirectMixin):
verify = merge_setting(verify, self.verify)
cert = merge_setting(cert, self.cert)
- return {'verify': verify, 'proxies': proxies, 'stream': stream,
- 'cert': cert}
+ return {"proxies": proxies, "stream": stream, "verify": verify, "cert": cert}
def get_adapter(self, url):
"""
@@ -739,7 +789,7 @@ class Session(SessionRedirectMixin):
return adapter
# Nothing matches :-/
- raise InvalidSchema("No connection adapters were found for {!r}".format(url))
+ raise InvalidSchema(f"No connection adapters were found for {url!r}")
def close(self):
"""Closes all adapters and as such the session"""
diff --git a/src/pip/_vendor/requests/status_codes.py b/src/pip/_vendor/requests/status_codes.py
index d80a7cd4d..4bd072be9 100644
--- a/src/pip/_vendor/requests/status_codes.py
+++ b/src/pip/_vendor/requests/status_codes.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
r"""
The ``codes`` object defines a mapping from common names for HTTP statuses
to their numerical codes, accessible either as attributes or as dictionary
@@ -23,101 +21,108 @@ the names are allowed. For example, ``codes.ok``, ``codes.OK``, and
from .structures import LookupDict
_codes = {
-
# Informational.
- 100: ('continue',),
- 101: ('switching_protocols',),
- 102: ('processing',),
- 103: ('checkpoint',),
- 122: ('uri_too_long', 'request_uri_too_long'),
- 200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '✓'),
- 201: ('created',),
- 202: ('accepted',),
- 203: ('non_authoritative_info', 'non_authoritative_information'),
- 204: ('no_content',),
- 205: ('reset_content', 'reset'),
- 206: ('partial_content', 'partial'),
- 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'),
- 208: ('already_reported',),
- 226: ('im_used',),
-
+ 100: ("continue",),
+ 101: ("switching_protocols",),
+ 102: ("processing",),
+ 103: ("checkpoint",),
+ 122: ("uri_too_long", "request_uri_too_long"),
+ 200: ("ok", "okay", "all_ok", "all_okay", "all_good", "\\o/", "✓"),
+ 201: ("created",),
+ 202: ("accepted",),
+ 203: ("non_authoritative_info", "non_authoritative_information"),
+ 204: ("no_content",),
+ 205: ("reset_content", "reset"),
+ 206: ("partial_content", "partial"),
+ 207: ("multi_status", "multiple_status", "multi_stati", "multiple_stati"),
+ 208: ("already_reported",),
+ 226: ("im_used",),
# Redirection.
- 300: ('multiple_choices',),
- 301: ('moved_permanently', 'moved', '\\o-'),
- 302: ('found',),
- 303: ('see_other', 'other'),
- 304: ('not_modified',),
- 305: ('use_proxy',),
- 306: ('switch_proxy',),
- 307: ('temporary_redirect', 'temporary_moved', 'temporary'),
- 308: ('permanent_redirect',
- 'resume_incomplete', 'resume',), # These 2 to be removed in 3.0
-
+ 300: ("multiple_choices",),
+ 301: ("moved_permanently", "moved", "\\o-"),
+ 302: ("found",),
+ 303: ("see_other", "other"),
+ 304: ("not_modified",),
+ 305: ("use_proxy",),
+ 306: ("switch_proxy",),
+ 307: ("temporary_redirect", "temporary_moved", "temporary"),
+ 308: (
+ "permanent_redirect",
+ "resume_incomplete",
+ "resume",
+ ), # "resume" and "resume_incomplete" to be removed in 3.0
# Client Error.
- 400: ('bad_request', 'bad'),
- 401: ('unauthorized',),
- 402: ('payment_required', 'payment'),
- 403: ('forbidden',),
- 404: ('not_found', '-o-'),
- 405: ('method_not_allowed', 'not_allowed'),
- 406: ('not_acceptable',),
- 407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'),
- 408: ('request_timeout', 'timeout'),
- 409: ('conflict',),
- 410: ('gone',),
- 411: ('length_required',),
- 412: ('precondition_failed', 'precondition'),
- 413: ('request_entity_too_large',),
- 414: ('request_uri_too_large',),
- 415: ('unsupported_media_type', 'unsupported_media', 'media_type'),
- 416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'),
- 417: ('expectation_failed',),
- 418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'),
- 421: ('misdirected_request',),
- 422: ('unprocessable_entity', 'unprocessable'),
- 423: ('locked',),
- 424: ('failed_dependency', 'dependency'),
- 425: ('unordered_collection', 'unordered'),
- 426: ('upgrade_required', 'upgrade'),
- 428: ('precondition_required', 'precondition'),
- 429: ('too_many_requests', 'too_many'),
- 431: ('header_fields_too_large', 'fields_too_large'),
- 444: ('no_response', 'none'),
- 449: ('retry_with', 'retry'),
- 450: ('blocked_by_windows_parental_controls', 'parental_controls'),
- 451: ('unavailable_for_legal_reasons', 'legal_reasons'),
- 499: ('client_closed_request',),
-
+ 400: ("bad_request", "bad"),
+ 401: ("unauthorized",),
+ 402: ("payment_required", "payment"),
+ 403: ("forbidden",),
+ 404: ("not_found", "-o-"),
+ 405: ("method_not_allowed", "not_allowed"),
+ 406: ("not_acceptable",),
+ 407: ("proxy_authentication_required", "proxy_auth", "proxy_authentication"),
+ 408: ("request_timeout", "timeout"),
+ 409: ("conflict",),
+ 410: ("gone",),
+ 411: ("length_required",),
+ 412: ("precondition_failed", "precondition"),
+ 413: ("request_entity_too_large",),
+ 414: ("request_uri_too_large",),
+ 415: ("unsupported_media_type", "unsupported_media", "media_type"),
+ 416: (
+ "requested_range_not_satisfiable",
+ "requested_range",
+ "range_not_satisfiable",
+ ),
+ 417: ("expectation_failed",),
+ 418: ("im_a_teapot", "teapot", "i_am_a_teapot"),
+ 421: ("misdirected_request",),
+ 422: ("unprocessable_entity", "unprocessable"),
+ 423: ("locked",),
+ 424: ("failed_dependency", "dependency"),
+ 425: ("unordered_collection", "unordered"),
+ 426: ("upgrade_required", "upgrade"),
+ 428: ("precondition_required", "precondition"),
+ 429: ("too_many_requests", "too_many"),
+ 431: ("header_fields_too_large", "fields_too_large"),
+ 444: ("no_response", "none"),
+ 449: ("retry_with", "retry"),
+ 450: ("blocked_by_windows_parental_controls", "parental_controls"),
+ 451: ("unavailable_for_legal_reasons", "legal_reasons"),
+ 499: ("client_closed_request",),
# Server Error.
- 500: ('internal_server_error', 'server_error', '/o\\', '✗'),
- 501: ('not_implemented',),
- 502: ('bad_gateway',),
- 503: ('service_unavailable', 'unavailable'),
- 504: ('gateway_timeout',),
- 505: ('http_version_not_supported', 'http_version'),
- 506: ('variant_also_negotiates',),
- 507: ('insufficient_storage',),
- 509: ('bandwidth_limit_exceeded', 'bandwidth'),
- 510: ('not_extended',),
- 511: ('network_authentication_required', 'network_auth', 'network_authentication'),
+ 500: ("internal_server_error", "server_error", "/o\\", "✗"),
+ 501: ("not_implemented",),
+ 502: ("bad_gateway",),
+ 503: ("service_unavailable", "unavailable"),
+ 504: ("gateway_timeout",),
+ 505: ("http_version_not_supported", "http_version"),
+ 506: ("variant_also_negotiates",),
+ 507: ("insufficient_storage",),
+ 509: ("bandwidth_limit_exceeded", "bandwidth"),
+ 510: ("not_extended",),
+ 511: ("network_authentication_required", "network_auth", "network_authentication"),
}
-codes = LookupDict(name='status_codes')
+codes = LookupDict(name="status_codes")
+
def _init():
for code, titles in _codes.items():
for title in titles:
setattr(codes, title, code)
- if not title.startswith(('\\', '/')):
+ if not title.startswith(("\\", "/")):
setattr(codes, title.upper(), code)
def doc(code):
- names = ', '.join('``%s``' % n for n in _codes[code])
- return '* %d: %s' % (code, names)
+ names = ", ".join(f"``{n}``" for n in _codes[code])
+ return "* %d: %s" % (code, names)
global __doc__
- __doc__ = (__doc__ + '\n' +
- '\n'.join(doc(code) for code in sorted(_codes))
- if __doc__ is not None else None)
+ __doc__ = (
+ __doc__ + "\n" + "\n".join(doc(code) for code in sorted(_codes))
+ if __doc__ is not None
+ else None
+ )
+
_init()
diff --git a/src/pip/_vendor/requests/structures.py b/src/pip/_vendor/requests/structures.py
index 8ee0ba7a0..188e13e48 100644
--- a/src/pip/_vendor/requests/structures.py
+++ b/src/pip/_vendor/requests/structures.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
"""
requests.structures
~~~~~~~~~~~~~~~~~~~
@@ -64,11 +62,7 @@ class CaseInsensitiveDict(MutableMapping):
def lower_items(self):
"""Like iteritems(), but with all lowercase keys."""
- return (
- (lowerkey, keyval[1])
- for (lowerkey, keyval)
- in self._store.items()
- )
+ return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items())
def __eq__(self, other):
if isinstance(other, Mapping):
@@ -91,10 +85,10 @@ class LookupDict(dict):
def __init__(self, name=None):
self.name = name
- super(LookupDict, self).__init__()
+ super().__init__()
def __repr__(self):
- return '<lookup \'%s\'>' % (self.name)
+ return f"<lookup '{self.name}'>"
def __getitem__(self, key):
# We allow fall-through here, so values default to None
diff --git a/src/pip/_vendor/requests/utils.py b/src/pip/_vendor/requests/utils.py
index fcb996690..33f394d26 100644
--- a/src/pip/_vendor/requests/utils.py
+++ b/src/pip/_vendor/requests/utils.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
"""
requests.utils
~~~~~~~~~~~~~~
@@ -20,27 +18,46 @@ import tempfile
import warnings
import zipfile
from collections import OrderedDict
-from pip._vendor.urllib3.util import make_headers
-from .__version__ import __version__
+from pip._vendor.urllib3.util import make_headers, parse_url
+
from . import certs
+from .__version__ import __version__
+
# to_native_string is unused here, but imported here for backwards compatibility
-from ._internal_utils import to_native_string
+from ._internal_utils import HEADER_VALIDATORS, to_native_string # noqa: F401
+from .compat import (
+ Mapping,
+ basestring,
+ bytes,
+ getproxies,
+ getproxies_environment,
+ integer_types,
+)
from .compat import parse_http_list as _parse_list_header
from .compat import (
- quote, urlparse, bytes, str, unquote, getproxies,
- proxy_bypass, urlunparse, basestring, integer_types, is_py3,
- proxy_bypass_environment, getproxies_environment, Mapping)
+ proxy_bypass,
+ proxy_bypass_environment,
+ quote,
+ str,
+ unquote,
+ urlparse,
+ urlunparse,
+)
from .cookies import cookiejar_from_dict
-from .structures import CaseInsensitiveDict
from .exceptions import (
- InvalidURL, InvalidHeader, FileModeWarning, UnrewindableBodyError)
+ FileModeWarning,
+ InvalidHeader,
+ InvalidURL,
+ UnrewindableBodyError,
+)
+from .structures import CaseInsensitiveDict
-NETRC_FILES = ('.netrc', '_netrc')
+NETRC_FILES = (".netrc", "_netrc")
DEFAULT_CA_BUNDLE_PATH = certs.where()
-DEFAULT_PORTS = {'http': 80, 'https': 443}
+DEFAULT_PORTS = {"http": 80, "https": 443}
# Ensure that ', ' is used to preserve previous delimiter behavior.
DEFAULT_ACCEPT_ENCODING = ", ".join(
@@ -48,28 +65,25 @@ DEFAULT_ACCEPT_ENCODING = ", ".join(
)
-if sys.platform == 'win32':
+if sys.platform == "win32":
# provide a proxy_bypass version on Windows without DNS lookups
def proxy_bypass_registry(host):
try:
- if is_py3:
- import winreg
- else:
- import _winreg as winreg
+ import winreg
except ImportError:
return False
try:
- internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
- r'Software\Microsoft\Windows\CurrentVersion\Internet Settings')
+ internetSettings = winreg.OpenKey(
+ winreg.HKEY_CURRENT_USER,
+ r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
+ )
# ProxyEnable could be REG_SZ or REG_DWORD, normalizing it
- proxyEnable = int(winreg.QueryValueEx(internetSettings,
- 'ProxyEnable')[0])
+ proxyEnable = int(winreg.QueryValueEx(internetSettings, "ProxyEnable")[0])
# ProxyOverride is almost always a string
- proxyOverride = winreg.QueryValueEx(internetSettings,
- 'ProxyOverride')[0]
- except OSError:
+ proxyOverride = winreg.QueryValueEx(internetSettings, "ProxyOverride")[0]
+ except (OSError, ValueError):
return False
if not proxyEnable or not proxyOverride:
return False
@@ -77,15 +91,15 @@ if sys.platform == 'win32':
# make a check value list from the registry entry: replace the
# '<local>' string by the localhost entry and the corresponding
# canonical entry.
- proxyOverride = proxyOverride.split(';')
+ proxyOverride = proxyOverride.split(";")
# now check if we match one of the registry values.
for test in proxyOverride:
- if test == '<local>':
- if '.' not in host:
+ if test == "<local>":
+ if "." not in host:
return True
- test = test.replace(".", r"\.") # mask dots
- test = test.replace("*", r".*") # change glob sequence
- test = test.replace("?", r".") # change glob char
+ test = test.replace(".", r"\.") # mask dots
+ test = test.replace("*", r".*") # change glob sequence
+ test = test.replace("?", r".") # change glob char
if re.match(test, host, re.I):
return True
return False
@@ -105,7 +119,7 @@ if sys.platform == 'win32':
def dict_to_sequence(d):
"""Returns an internal sequence dictionary update."""
- if hasattr(d, 'items'):
+ if hasattr(d, "items"):
d = d.items()
return d
@@ -115,37 +129,42 @@ def super_len(o):
total_length = None
current_position = 0
- if hasattr(o, '__len__'):
+ if hasattr(o, "__len__"):
total_length = len(o)
- elif hasattr(o, 'len'):
+ elif hasattr(o, "len"):
total_length = o.len
- elif hasattr(o, 'fileno'):
+ elif hasattr(o, "fileno"):
try:
fileno = o.fileno()
- except io.UnsupportedOperation:
+ except (io.UnsupportedOperation, AttributeError):
+ # AttributeError is a surprising exception, seeing as how we've just checked
+ # that `hasattr(o, 'fileno')`. It happens for objects obtained via
+ # `Tarfile.extractfile()`, per issue 5229.
pass
else:
total_length = os.fstat(fileno).st_size
# Having used fstat to determine the file length, we need to
# confirm that this file was opened up in binary mode.
- if 'b' not in o.mode:
- warnings.warn((
- "Requests has determined the content-length for this "
- "request using the binary size of the file: however, the "
- "file has been opened in text mode (i.e. without the 'b' "
- "flag in the mode). This may lead to an incorrect "
- "content-length. In Requests 3.0, support will be removed "
- "for files in text mode."),
- FileModeWarning
+ if "b" not in o.mode:
+ warnings.warn(
+ (
+ "Requests has determined the content-length for this "
+ "request using the binary size of the file: however, the "
+ "file has been opened in text mode (i.e. without the 'b' "
+ "flag in the mode). This may lead to an incorrect "
+ "content-length. In Requests 3.0, support will be removed "
+ "for files in text mode."
+ ),
+ FileModeWarning,
)
- if hasattr(o, 'tell'):
+ if hasattr(o, "tell"):
try:
current_position = o.tell()
- except (OSError, IOError):
+ except OSError:
# This can happen in some weird situations, such as when the file
# is actually a special file descriptor like stdin. In this
# instance, we don't know what the length is, so set it to zero and
@@ -153,8 +172,8 @@ def super_len(o):
if total_length is not None:
current_position = total_length
else:
- if hasattr(o, 'seek') and total_length is None:
- # StringIO and BytesIO have seek but no useable fileno
+ if hasattr(o, "seek") and total_length is None:
+ # StringIO and BytesIO have seek but no usable fileno
try:
# seek to end of file
o.seek(0, 2)
@@ -163,7 +182,7 @@ def super_len(o):
# seek back to current position to support
# partially read file-like objects
o.seek(current_position or 0)
- except (OSError, IOError):
+ except OSError:
total_length = 0
if total_length is None:
@@ -175,14 +194,14 @@ def super_len(o):
def get_netrc_auth(url, raise_errors=False):
"""Returns the Requests tuple auth for a given url from netrc."""
- netrc_file = os.environ.get('NETRC')
+ netrc_file = os.environ.get("NETRC")
if netrc_file is not None:
netrc_locations = (netrc_file,)
else:
- netrc_locations = ('~/{}'.format(f) for f in NETRC_FILES)
+ netrc_locations = (f"~/{f}" for f in NETRC_FILES)
try:
- from netrc import netrc, NetrcParseError
+ from netrc import NetrcParseError, netrc
netrc_path = None
@@ -207,18 +226,18 @@ def get_netrc_auth(url, raise_errors=False):
# Strip port numbers from netloc. This weird `if...encode`` dance is
# used for Python 3.2, which doesn't support unicode literals.
- splitstr = b':'
+ splitstr = b":"
if isinstance(url, str):
- splitstr = splitstr.decode('ascii')
+ splitstr = splitstr.decode("ascii")
host = ri.netloc.split(splitstr)[0]
try:
_netrc = netrc(netrc_path).authenticators(host)
if _netrc:
# Return with login / password
- login_i = (0 if _netrc[0] else 1)
+ login_i = 0 if _netrc[0] else 1
return (_netrc[login_i], _netrc[2])
- except (NetrcParseError, IOError):
+ except (NetrcParseError, OSError):
# If there was a parsing error or a permissions issue reading the file,
# we'll just skip netrc auth unless explicitly asked to raise errors.
if raise_errors:
@@ -231,9 +250,8 @@ def get_netrc_auth(url, raise_errors=False):
def guess_filename(obj):
"""Tries to guess the filename of the given object."""
- name = getattr(obj, 'name', None)
- if (name and isinstance(name, basestring) and name[0] != '<' and
- name[-1] != '>'):
+ name = getattr(obj, "name", None)
+ if name and isinstance(name, basestring) and name[0] != "<" and name[-1] != ">":
return os.path.basename(name)
@@ -251,7 +269,11 @@ def extract_zipped_paths(path):
archive, member = os.path.split(path)
while archive and not os.path.exists(archive):
archive, prefix = os.path.split(archive)
- member = '/'.join([prefix, member])
+ if not prefix:
+ # If we don't check for an empty prefix after the split (in other words, archive remains unchanged after the split),
+ # we _can_ end up in an infinite loop on a rare corner case affecting a small number of users
+ break
+ member = "/".join([prefix, member])
if not zipfile.is_zipfile(archive):
return path
@@ -262,7 +284,7 @@ def extract_zipped_paths(path):
# we have a valid zip archive and a valid member of that archive
tmp = tempfile.gettempdir()
- extracted_path = os.path.join(tmp, member.split('/')[-1])
+ extracted_path = os.path.join(tmp, member.split("/")[-1])
if not os.path.exists(extracted_path):
# use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition
with atomic_open(extracted_path) as file_handler:
@@ -273,12 +295,11 @@ def extract_zipped_paths(path):
@contextlib.contextmanager
def atomic_open(filename):
"""Write a file to the disk in an atomic fashion"""
- replacer = os.rename if sys.version_info[0] == 2 else os.replace
tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename))
try:
- with os.fdopen(tmp_descriptor, 'wb') as tmp_handler:
+ with os.fdopen(tmp_descriptor, "wb") as tmp_handler:
yield tmp_handler
- replacer(tmp_name, filename)
+ os.replace(tmp_name, filename)
except BaseException:
os.remove(tmp_name)
raise
@@ -306,7 +327,7 @@ def from_key_val_list(value):
return None
if isinstance(value, (str, bytes, bool, int)):
- raise ValueError('cannot encode objects that are not 2-tuples')
+ raise ValueError("cannot encode objects that are not 2-tuples")
return OrderedDict(value)
@@ -332,7 +353,7 @@ def to_key_val_list(value):
return None
if isinstance(value, (str, bytes, bool, int)):
- raise ValueError('cannot encode objects that are not 2-tuples')
+ raise ValueError("cannot encode objects that are not 2-tuples")
if isinstance(value, Mapping):
value = value.items()
@@ -397,10 +418,10 @@ def parse_dict_header(value):
"""
result = {}
for item in _parse_list_header(value):
- if '=' not in item:
+ if "=" not in item:
result[item] = None
continue
- name, value = item.split('=', 1)
+ name, value = item.split("=", 1)
if value[:1] == value[-1:] == '"':
value = unquote_header_value(value[1:-1])
result[name] = value
@@ -428,8 +449,8 @@ def unquote_header_value(value, is_filename=False):
# replace sequence below on a UNC path has the effect of turning
# the leading double slash into a single slash and then
# _fix_ie_filename() doesn't work correctly. See #458.
- if not is_filename or value[:2] != '\\\\':
- return value.replace('\\\\', '\\').replace('\\"', '"')
+ if not is_filename or value[:2] != "\\\\":
+ return value.replace("\\\\", "\\").replace('\\"', '"')
return value
@@ -464,19 +485,24 @@ def get_encodings_from_content(content):
:param content: bytestring to extract encodings from.
"""
- warnings.warn((
- 'In requests 3.0, get_encodings_from_content will be removed. For '
- 'more information, please see the discussion on issue #2266. (This'
- ' warning should only appear once.)'),
- DeprecationWarning)
+ warnings.warn(
+ (
+ "In requests 3.0, get_encodings_from_content will be removed. For "
+ "more information, please see the discussion on issue #2266. (This"
+ " warning should only appear once.)"
+ ),
+ DeprecationWarning,
+ )
charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I)
xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]')
- return (charset_re.findall(content) +
- pragma_re.findall(content) +
- xml_re.findall(content))
+ return (
+ charset_re.findall(content)
+ + pragma_re.findall(content)
+ + xml_re.findall(content)
+ )
def _parse_content_type_header(header):
@@ -487,7 +513,7 @@ def _parse_content_type_header(header):
parameters
"""
- tokens = header.split(';')
+ tokens = header.split(";")
content_type, params = tokens[0].strip(), tokens[1:]
params_dict = {}
items_to_strip = "\"' "
@@ -499,7 +525,7 @@ def _parse_content_type_header(header):
index_of_equals = param.find("=")
if index_of_equals != -1:
key = param[:index_of_equals].strip(items_to_strip)
- value = param[index_of_equals + 1:].strip(items_to_strip)
+ value = param[index_of_equals + 1 :].strip(items_to_strip)
params_dict[key.lower()] = value
return content_type, params_dict
@@ -511,38 +537,37 @@ def get_encoding_from_headers(headers):
:rtype: str
"""
- content_type = headers.get('content-type')
+ content_type = headers.get("content-type")
if not content_type:
return None
content_type, params = _parse_content_type_header(content_type)
- if 'charset' in params:
- return params['charset'].strip("'\"")
+ if "charset" in params:
+ return params["charset"].strip("'\"")
- if 'text' in content_type:
- return 'ISO-8859-1'
+ if "text" in content_type:
+ return "ISO-8859-1"
- if 'application/json' in content_type:
+ if "application/json" in content_type:
# Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset
- return 'utf-8'
+ return "utf-8"
def stream_decode_response_unicode(iterator, r):
- """Stream decodes a iterator."""
+ """Stream decodes an iterator."""
if r.encoding is None:
- for item in iterator:
- yield item
+ yield from iterator
return
- decoder = codecs.getincrementaldecoder(r.encoding)(errors='replace')
+ decoder = codecs.getincrementaldecoder(r.encoding)(errors="replace")
for chunk in iterator:
rv = decoder.decode(chunk)
if rv:
yield rv
- rv = decoder.decode(b'', final=True)
+ rv = decoder.decode(b"", final=True)
if rv:
yield rv
@@ -553,7 +578,7 @@ def iter_slices(string, slice_length):
if slice_length is None or slice_length <= 0:
slice_length = len(string)
while pos < len(string):
- yield string[pos:pos + slice_length]
+ yield string[pos : pos + slice_length]
pos += slice_length
@@ -569,11 +594,14 @@ def get_unicode_from_response(r):
:rtype: str
"""
- warnings.warn((
- 'In requests 3.0, get_unicode_from_response will be removed. For '
- 'more information, please see the discussion on issue #2266. (This'
- ' warning should only appear once.)'),
- DeprecationWarning)
+ warnings.warn(
+ (
+ "In requests 3.0, get_unicode_from_response will be removed. For "
+ "more information, please see the discussion on issue #2266. (This"
+ " warning should only appear once.)"
+ ),
+ DeprecationWarning,
+ )
tried_encodings = []
@@ -588,14 +616,15 @@ def get_unicode_from_response(r):
# Fall back:
try:
- return str(r.content, encoding, errors='replace')
+ return str(r.content, encoding, errors="replace")
except TypeError:
return r.content
# The unreserved URI characters (RFC 3986)
UNRESERVED_SET = frozenset(
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~")
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~"
+)
def unquote_unreserved(uri):
@@ -604,22 +633,22 @@ def unquote_unreserved(uri):
:rtype: str
"""
- parts = uri.split('%')
+ parts = uri.split("%")
for i in range(1, len(parts)):
h = parts[i][0:2]
if len(h) == 2 and h.isalnum():
try:
c = chr(int(h, 16))
except ValueError:
- raise InvalidURL("Invalid percent-escape sequence: '%s'" % h)
+ raise InvalidURL(f"Invalid percent-escape sequence: '{h}'")
if c in UNRESERVED_SET:
parts[i] = c + parts[i][2:]
else:
- parts[i] = '%' + parts[i]
+ parts[i] = f"%{parts[i]}"
else:
- parts[i] = '%' + parts[i]
- return ''.join(parts)
+ parts[i] = f"%{parts[i]}"
+ return "".join(parts)
def requote_uri(uri):
@@ -652,10 +681,10 @@ def address_in_network(ip, net):
:rtype: bool
"""
- ipaddr = struct.unpack('=L', socket.inet_aton(ip))[0]
- netaddr, bits = net.split('/')
- netmask = struct.unpack('=L', socket.inet_aton(dotted_netmask(int(bits))))[0]
- network = struct.unpack('=L', socket.inet_aton(netaddr))[0] & netmask
+ ipaddr = struct.unpack("=L", socket.inet_aton(ip))[0]
+ netaddr, bits = net.split("/")
+ netmask = struct.unpack("=L", socket.inet_aton(dotted_netmask(int(bits))))[0]
+ network = struct.unpack("=L", socket.inet_aton(netaddr))[0] & netmask
return (ipaddr & netmask) == (network & netmask)
@@ -666,8 +695,8 @@ def dotted_netmask(mask):
:rtype: str
"""
- bits = 0xffffffff ^ (1 << 32 - mask) - 1
- return socket.inet_ntoa(struct.pack('>I', bits))
+ bits = 0xFFFFFFFF ^ (1 << 32 - mask) - 1
+ return socket.inet_ntoa(struct.pack(">I", bits))
def is_ipv4_address(string_ip):
@@ -676,7 +705,7 @@ def is_ipv4_address(string_ip):
"""
try:
socket.inet_aton(string_ip)
- except socket.error:
+ except OSError:
return False
return True
@@ -687,9 +716,9 @@ def is_valid_cidr(string_network):
:rtype: bool
"""
- if string_network.count('/') == 1:
+ if string_network.count("/") == 1:
try:
- mask = int(string_network.split('/')[1])
+ mask = int(string_network.split("/")[1])
except ValueError:
return False
@@ -697,8 +726,8 @@ def is_valid_cidr(string_network):
return False
try:
- socket.inet_aton(string_network.split('/')[0])
- except socket.error:
+ socket.inet_aton(string_network.split("/")[0])
+ except OSError:
return False
else:
return False
@@ -735,13 +764,14 @@ def should_bypass_proxies(url, no_proxy):
"""
# Prioritize lowercase environment variables over uppercase
# to keep a consistent behaviour with other http projects (curl, wget).
- get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper())
+ def get_proxy(key):
+ return os.environ.get(key) or os.environ.get(key.upper())
# First check whether no_proxy is defined. If it is, check that the URL
# we're getting isn't in the no_proxy list.
no_proxy_arg = no_proxy
if no_proxy is None:
- no_proxy = get_proxy('no_proxy')
+ no_proxy = get_proxy("no_proxy")
parsed = urlparse(url)
if parsed.hostname is None:
@@ -751,9 +781,7 @@ def should_bypass_proxies(url, no_proxy):
if no_proxy:
# We need to check whether we match here. We need to see if we match
# the end of the hostname, both with and without the port.
- no_proxy = (
- host for host in no_proxy.replace(' ', '').split(',') if host
- )
+ no_proxy = (host for host in no_proxy.replace(" ", "").split(",") if host)
if is_ipv4_address(parsed.hostname):
for proxy_ip in no_proxy:
@@ -767,7 +795,7 @@ def should_bypass_proxies(url, no_proxy):
else:
host_with_port = parsed.hostname
if parsed.port:
- host_with_port += ':{}'.format(parsed.port)
+ host_with_port += f":{parsed.port}"
for host in no_proxy:
if parsed.hostname.endswith(host) or host_with_port.endswith(host):
@@ -775,7 +803,7 @@ def should_bypass_proxies(url, no_proxy):
# to apply the proxies on this URL.
return True
- with set_environ('no_proxy', no_proxy_arg):
+ with set_environ("no_proxy", no_proxy_arg):
# parsed.hostname can be `None` in cases such as a file URI.
try:
bypass = proxy_bypass(parsed.hostname)
@@ -809,13 +837,13 @@ def select_proxy(url, proxies):
proxies = proxies or {}
urlparts = urlparse(url)
if urlparts.hostname is None:
- return proxies.get(urlparts.scheme, proxies.get('all'))
+ return proxies.get(urlparts.scheme, proxies.get("all"))
proxy_keys = [
- urlparts.scheme + '://' + urlparts.hostname,
+ urlparts.scheme + "://" + urlparts.hostname,
urlparts.scheme,
- 'all://' + urlparts.hostname,
- 'all',
+ "all://" + urlparts.hostname,
+ "all",
]
proxy = None
for proxy_key in proxy_keys:
@@ -826,25 +854,54 @@ def select_proxy(url, proxies):
return proxy
+def resolve_proxies(request, proxies, trust_env=True):
+ """This method takes proxy information from a request and configuration
+ input to resolve a mapping of target proxies. This will consider settings
+ such a NO_PROXY to strip proxy configurations.
+
+ :param request: Request or PreparedRequest
+ :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs
+ :param trust_env: Boolean declaring whether to trust environment configs
+
+ :rtype: dict
+ """
+ proxies = proxies if proxies is not None else {}
+ url = request.url
+ scheme = urlparse(url).scheme
+ no_proxy = proxies.get("no_proxy")
+ new_proxies = proxies.copy()
+
+ if trust_env and not should_bypass_proxies(url, no_proxy=no_proxy):
+ environ_proxies = get_environ_proxies(url, no_proxy=no_proxy)
+
+ proxy = environ_proxies.get(scheme, environ_proxies.get("all"))
+
+ if proxy:
+ new_proxies.setdefault(scheme, proxy)
+ return new_proxies
+
+
def default_user_agent(name="python-requests"):
"""
Return a string representing the default user agent.
:rtype: str
"""
- return '%s/%s' % (name, __version__)
+ return f"{name}/{__version__}"
def default_headers():
"""
:rtype: requests.structures.CaseInsensitiveDict
"""
- return CaseInsensitiveDict({
- 'User-Agent': default_user_agent(),
- 'Accept-Encoding': DEFAULT_ACCEPT_ENCODING,
- 'Accept': '*/*',
- 'Connection': 'keep-alive',
- })
+ return CaseInsensitiveDict(
+ {
+ "User-Agent": default_user_agent(),
+ "Accept-Encoding": DEFAULT_ACCEPT_ENCODING,
+ "Accept": "*/*",
+ "Connection": "keep-alive",
+ }
+ )
def parse_header_links(value):
@@ -857,23 +914,23 @@ def parse_header_links(value):
links = []
- replace_chars = ' \'"'
+ replace_chars = " '\""
value = value.strip(replace_chars)
if not value:
return links
- for val in re.split(', *<', value):
+ for val in re.split(", *<", value):
try:
- url, params = val.split(';', 1)
+ url, params = val.split(";", 1)
except ValueError:
- url, params = val, ''
+ url, params = val, ""
- link = {'url': url.strip('<> \'"')}
+ link = {"url": url.strip("<> '\"")}
- for param in params.split(';'):
+ for param in params.split(";"):
try:
- key, value = param.split('=')
+ key, value = param.split("=")
except ValueError:
break
@@ -885,7 +942,7 @@ def parse_header_links(value):
# Null bytes; no need to recreate these on each call to guess_json_utf
-_null = '\x00'.encode('ascii') # encoding to ASCII for Python 3
+_null = "\x00".encode("ascii") # encoding to ASCII for Python 3
_null2 = _null * 2
_null3 = _null * 3
@@ -899,25 +956,25 @@ def guess_json_utf(data):
# determine the encoding. Also detect a BOM, if present.
sample = data[:4]
if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE):
- return 'utf-32' # BOM included
+ return "utf-32" # BOM included
if sample[:3] == codecs.BOM_UTF8:
- return 'utf-8-sig' # BOM included, MS style (discouraged)
+ return "utf-8-sig" # BOM included, MS style (discouraged)
if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE):
- return 'utf-16' # BOM included
+ return "utf-16" # BOM included
nullcount = sample.count(_null)
if nullcount == 0:
- return 'utf-8'
+ return "utf-8"
if nullcount == 2:
- if sample[::2] == _null2: # 1st and 3rd are null
- return 'utf-16-be'
+ if sample[::2] == _null2: # 1st and 3rd are null
+ return "utf-16-be"
if sample[1::2] == _null2: # 2nd and 4th are null
- return 'utf-16-le'
+ return "utf-16-le"
# Did not detect 2 valid UTF-16 ascii-range characters
if nullcount == 3:
if sample[:3] == _null3:
- return 'utf-32-be'
+ return "utf-32-be"
if sample[1:] == _null3:
- return 'utf-32-le'
+ return "utf-32-le"
# Did not detect a valid UTF-32 ascii-range character
return None
@@ -928,15 +985,27 @@ def prepend_scheme_if_needed(url, new_scheme):
:rtype: str
"""
- scheme, netloc, path, params, query, fragment = urlparse(url, new_scheme)
-
- # urlparse is a finicky beast, and sometimes decides that there isn't a
- # netloc present. Assume that it's being over-cautious, and switch netloc
- # and path if urlparse decided there was no netloc.
+ parsed = parse_url(url)
+ scheme, auth, host, port, path, query, fragment = parsed
+
+ # A defect in urlparse determines that there isn't a netloc present in some
+ # urls. We previously assumed parsing was overly cautious, and swapped the
+ # netloc and path. Due to a lack of tests on the original defect, this is
+ # maintained with parse_url for backwards compatibility.
+ netloc = parsed.netloc
if not netloc:
netloc, path = path, netloc
- return urlunparse((scheme, netloc, path, params, query, fragment))
+ if auth:
+ # parse_url doesn't provide the netloc with auth
+ # so we'll add it ourselves.
+ netloc = "@".join([auth, netloc])
+ if scheme is None:
+ scheme = new_scheme
+ if path is None:
+ path = ""
+
+ return urlunparse((scheme, netloc, path, "", query, fragment))
def get_auth_from_url(url):
@@ -950,35 +1019,36 @@ def get_auth_from_url(url):
try:
auth = (unquote(parsed.username), unquote(parsed.password))
except (AttributeError, TypeError):
- auth = ('', '')
+ auth = ("", "")
return auth
-# Moved outside of function to avoid recompile every call
-_CLEAN_HEADER_REGEX_BYTE = re.compile(b'^\\S[^\\r\\n]*$|^$')
-_CLEAN_HEADER_REGEX_STR = re.compile(r'^\S[^\r\n]*$|^$')
-
-
def check_header_validity(header):
- """Verifies that header value is a string which doesn't contain
- leading whitespace or return characters. This prevents unintended
- header injection.
+ """Verifies that header parts don't contain leading whitespace
+ reserved characters, or return characters.
:param header: tuple, in the format (name, value).
"""
name, value = header
- if isinstance(value, bytes):
- pat = _CLEAN_HEADER_REGEX_BYTE
- else:
- pat = _CLEAN_HEADER_REGEX_STR
- try:
- if not pat.match(value):
- raise InvalidHeader("Invalid return character or leading space in header: %s" % name)
- except TypeError:
- raise InvalidHeader("Value for header {%s: %s} must be of type str or "
- "bytes, not %s" % (name, value, type(value)))
+ for part in header:
+ if type(part) not in HEADER_VALIDATORS:
+ raise InvalidHeader(
+ f"Header part ({part!r}) from {{{name!r}: {value!r}}} must be "
+ f"of type str or bytes, not {type(part)}"
+ )
+
+ _validate_header_part(name, "name", HEADER_VALIDATORS[type(name)][0])
+ _validate_header_part(value, "value", HEADER_VALIDATORS[type(value)][1])
+
+
+def _validate_header_part(header_part, header_kind, validator):
+ if not validator.match(header_part):
+ raise InvalidHeader(
+ f"Invalid leading whitespace, reserved character(s), or return"
+ f"character(s) in header {header_kind}: {header_part!r}"
+ )
def urldefragauth(url):
@@ -993,21 +1063,24 @@ def urldefragauth(url):
if not netloc:
netloc, path = path, netloc
- netloc = netloc.rsplit('@', 1)[-1]
+ netloc = netloc.rsplit("@", 1)[-1]
- return urlunparse((scheme, netloc, path, params, query, ''))
+ return urlunparse((scheme, netloc, path, params, query, ""))
def rewind_body(prepared_request):
"""Move file pointer back to its recorded starting position
so it can be read again on redirect.
"""
- body_seek = getattr(prepared_request.body, 'seek', None)
- if body_seek is not None and isinstance(prepared_request._body_position, integer_types):
+ body_seek = getattr(prepared_request.body, "seek", None)
+ if body_seek is not None and isinstance(
+ prepared_request._body_position, integer_types
+ ):
try:
body_seek(prepared_request._body_position)
- except (IOError, OSError):
- raise UnrewindableBodyError("An error occurred when rewinding request "
- "body for redirect.")
+ except OSError:
+ raise UnrewindableBodyError(
+ "An error occurred when rewinding request body for redirect."
+ )
else:
raise UnrewindableBodyError("Unable to rewind request body for redirect.")
diff --git a/src/pip/_vendor/resolvelib/__init__.py b/src/pip/_vendor/resolvelib/__init__.py
index ce05fd302..fa6995e32 100644
--- a/src/pip/_vendor/resolvelib/__init__.py
+++ b/src/pip/_vendor/resolvelib/__init__.py
@@ -11,7 +11,7 @@ __all__ = [
"ResolutionTooDeep",
]
-__version__ = "0.8.1"
+__version__ = "0.9.0"
from .providers import AbstractProvider, AbstractResolver
diff --git a/src/pip/_vendor/resolvelib/compat/collections_abc.pyi b/src/pip/_vendor/resolvelib/compat/collections_abc.pyi
new file mode 100644
index 000000000..2a088b19a
--- /dev/null
+++ b/src/pip/_vendor/resolvelib/compat/collections_abc.pyi
@@ -0,0 +1 @@
+from collections.abc import Mapping, Sequence
diff --git a/src/pip/_vendor/resolvelib/providers.py b/src/pip/_vendor/resolvelib/providers.py
index 7d0a9c22a..e99d87ee7 100644
--- a/src/pip/_vendor/resolvelib/providers.py
+++ b/src/pip/_vendor/resolvelib/providers.py
@@ -1,5 +1,5 @@
class AbstractProvider(object):
- """Delegate class to provide requirement interface for the resolver."""
+ """Delegate class to provide the required interface for the resolver."""
def identify(self, requirement_or_candidate):
"""Given a requirement, return an identifier for it.
@@ -24,9 +24,9 @@ class AbstractProvider(object):
this group of arguments is.
:param identifier: An identifier as returned by ``identify()``. This
- identifies the dependency matches of which should be returned.
+ identifies the dependency matches which should be returned.
:param resolutions: Mapping of candidates currently pinned by the
- resolver. Each key is an identifier, and the value a candidate.
+ resolver. Each key is an identifier, and the value is a candidate.
The candidate may conflict with requirements from ``information``.
:param candidates: Mapping of each dependency's possible candidates.
Each value is an iterator of candidates.
@@ -39,10 +39,10 @@ class AbstractProvider(object):
* ``requirement`` specifies a requirement contributing to the current
list of candidates.
- * ``parent`` specifies the candidate that provides (dependend on) the
+ * ``parent`` specifies the candidate that provides (depended on) the
requirement, or ``None`` to indicate a root requirement.
- The preference could depend on a various of issues, including (not
+ The preference could depend on various issues, including (not
necessarily in this order):
* Is this package pinned in the current resolution result?
@@ -61,7 +61,7 @@ class AbstractProvider(object):
raise NotImplementedError
def find_matches(self, identifier, requirements, incompatibilities):
- """Find all possible candidates that satisfy given constraints.
+ """Find all possible candidates that satisfy the given constraints.
:param identifier: An identifier as returned by ``identify()``. This
identifies the dependency matches of which should be returned.
@@ -92,7 +92,7 @@ class AbstractProvider(object):
def is_satisfied_by(self, requirement, candidate):
"""Whether the given requirement can be satisfied by a candidate.
- The candidate is guarenteed to have been generated from the
+ The candidate is guaranteed to have been generated from the
requirement.
A boolean should be returned to indicate whether ``candidate`` is a
diff --git a/src/pip/_vendor/resolvelib/providers.pyi b/src/pip/_vendor/resolvelib/providers.pyi
index 47d6f8aba..ec054194e 100644
--- a/src/pip/_vendor/resolvelib/providers.pyi
+++ b/src/pip/_vendor/resolvelib/providers.pyi
@@ -1,12 +1,11 @@
from typing import (
Any,
- Collection,
Generic,
Iterable,
Iterator,
Mapping,
- Optional,
Protocol,
+ Sequence,
Union,
)
@@ -25,6 +24,7 @@ class AbstractProvider(Generic[RT, CT, KT]):
resolutions: Mapping[KT, CT],
candidates: Mapping[KT, Iterator[CT]],
information: Mapping[KT, Iterator[RequirementInformation[RT, CT]]],
+ backtrack_causes: Sequence[RequirementInformation[RT, CT]],
) -> Preference: ...
def find_matches(
self,
diff --git a/src/pip/_vendor/resolvelib/reporters.py b/src/pip/_vendor/resolvelib/reporters.py
index 6695480ff..688b5e10d 100644
--- a/src/pip/_vendor/resolvelib/reporters.py
+++ b/src/pip/_vendor/resolvelib/reporters.py
@@ -36,7 +36,7 @@ class BaseReporter(object):
:param causes: The information on the collision that caused the backtracking.
"""
- def backtracking(self, candidate):
+ def rejecting_candidate(self, criterion, candidate):
"""Called when rejecting a candidate during backtracking."""
def pinning(self, candidate):
diff --git a/src/pip/_vendor/resolvelib/reporters.pyi b/src/pip/_vendor/resolvelib/reporters.pyi
index 03d4f09a3..b2ad286ba 100644
--- a/src/pip/_vendor/resolvelib/reporters.pyi
+++ b/src/pip/_vendor/resolvelib/reporters.pyi
@@ -6,6 +6,6 @@ class BaseReporter:
def ending_round(self, index: int, state: Any) -> Any: ...
def ending(self, state: Any) -> Any: ...
def adding_requirement(self, requirement: Any, parent: Any) -> Any: ...
- def backtracking(self, candidate: Any) -> Any: ...
+ def rejecting_candidate(self, criterion: Any, candidate: Any) -> Any: ...
def resolving_conflicts(self, causes: Any) -> Any: ...
def pinning(self, candidate: Any) -> Any: ...
diff --git a/src/pip/_vendor/resolvelib/resolvers.py b/src/pip/_vendor/resolvelib/resolvers.py
index 787681b03..49e30c7f5 100644
--- a/src/pip/_vendor/resolvelib/resolvers.py
+++ b/src/pip/_vendor/resolvelib/resolvers.py
@@ -173,6 +173,31 @@ class Resolution(object):
raise RequirementsConflicted(criterion)
criteria[identifier] = criterion
+ def _remove_information_from_criteria(self, criteria, parents):
+ """Remove information from parents of criteria.
+
+ Concretely, removes all values from each criterion's ``information``
+ field that have one of ``parents`` as provider of the requirement.
+
+ :param criteria: The criteria to update.
+ :param parents: Identifiers for which to remove information from all criteria.
+ """
+ if not parents:
+ return
+ for key, criterion in criteria.items():
+ criteria[key] = Criterion(
+ criterion.candidates,
+ [
+ information
+ for information in criterion.information
+ if (
+ information[1] is None
+ or self._p.identify(information[1]) not in parents
+ )
+ ],
+ criterion.incompatibilities,
+ )
+
def _get_preference(self, name):
return self._p.get_preference(
identifier=name,
@@ -212,6 +237,7 @@ class Resolution(object):
try:
criteria = self._get_updated_criteria(candidate)
except RequirementsConflicted as e:
+ self._r.rejecting_candidate(e.criterion, candidate)
causes.append(e.criterion)
continue
@@ -281,8 +307,6 @@ class Resolution(object):
# Also mark the newly known incompatibility.
incompatibilities_from_broken.append((name, [candidate]))
- self._r.backtracking(candidate=candidate)
-
# Create a new state from the last known-to-work one, and apply
# the previously gathered incompatibility information.
def _patch_criteria():
@@ -368,6 +392,11 @@ class Resolution(object):
self._r.ending(state=self.state)
return self.state
+ # keep track of satisfied names to calculate diff after pinning
+ satisfied_names = set(self.state.criteria.keys()) - set(
+ unsatisfied_names
+ )
+
# Choose the most preferred unpinned criterion to try.
name = min(unsatisfied_names, key=self._get_preference)
failure_causes = self._attempt_to_pin_criterion(name)
@@ -384,6 +413,17 @@ class Resolution(object):
if not success:
raise ResolutionImpossible(self.state.backtrack_causes)
else:
+ # discard as information sources any invalidated names
+ # (unsatisfied names that were previously satisfied)
+ newly_unsatisfied_names = {
+ key
+ for key, criterion in self.state.criteria.items()
+ if key in satisfied_names
+ and not self._is_current_pin_satisfying(key, criterion)
+ }
+ self._remove_information_from_criteria(
+ self.state.criteria, newly_unsatisfied_names
+ )
# Pinning was successful. Push a new state to do another pin.
self._push_new_state()
diff --git a/src/pip/_vendor/resolvelib/resolvers.pyi b/src/pip/_vendor/resolvelib/resolvers.pyi
index 0eb5b2162..528a1a259 100644
--- a/src/pip/_vendor/resolvelib/resolvers.pyi
+++ b/src/pip/_vendor/resolvelib/resolvers.pyi
@@ -55,6 +55,18 @@ class ResolutionImpossible(ResolutionError, Generic[RT, CT]):
class ResolutionTooDeep(ResolutionError):
round_count: int
+# This should be a NamedTuple, but Python 3.6 has a bug that prevents it.
+# https://stackoverflow.com/a/50531189/1376863
+class State(tuple, Generic[RT, CT, KT]):
+ mapping: Mapping[KT, CT]
+ criteria: Mapping[KT, Criterion[RT, CT, KT]]
+ backtrack_causes: Collection[RequirementInformation[RT, CT]]
+
+class Resolution(Generic[RT, CT, KT]):
+ def resolve(
+ self, requirements: Iterable[RT], max_rounds: int
+ ) -> State[RT, CT, KT]: ...
+
class Result(Generic[RT, CT, KT]):
mapping: Mapping[KT, CT]
graph: DirectedGraph[Optional[KT]]
diff --git a/src/pip/_vendor/resolvelib/structs.py b/src/pip/_vendor/resolvelib/structs.py
index 93d1568bd..359a34f60 100644
--- a/src/pip/_vendor/resolvelib/structs.py
+++ b/src/pip/_vendor/resolvelib/structs.py
@@ -117,13 +117,14 @@ class _FactoryIterableView(object):
def __init__(self, factory):
self._factory = factory
+ self._iterable = None
def __repr__(self):
- return "{}({})".format(type(self).__name__, list(self._factory()))
+ return "{}({})".format(type(self).__name__, list(self))
def __bool__(self):
try:
- next(self._factory())
+ next(iter(self))
except StopIteration:
return False
return True
@@ -131,7 +132,11 @@ class _FactoryIterableView(object):
__nonzero__ = __bool__ # XXX: Python 2.
def __iter__(self):
- return self._factory()
+ iterable = (
+ self._factory() if self._iterable is None else self._iterable
+ )
+ self._iterable, current = itertools.tee(iterable)
+ return current
class _SequenceIterableView(object):
diff --git a/src/pip/_vendor/resolvelib/structs.pyi b/src/pip/_vendor/resolvelib/structs.pyi
index fae2a2fce..0ac59f0f0 100644
--- a/src/pip/_vendor/resolvelib/structs.pyi
+++ b/src/pip/_vendor/resolvelib/structs.pyi
@@ -16,7 +16,7 @@ RT = TypeVar("RT") # Requirement.
CT = TypeVar("CT") # Candidate.
_T = TypeVar("_T")
-Matches = Union[Iterable[CT], Callable[[], Iterator[CT]]]
+Matches = Union[Iterable[CT], Callable[[], Iterable[CT]]]
class IteratorMapping(Mapping[KT, _T], metaclass=ABCMeta):
pass
diff --git a/src/pip/_vendor/rich/LICENSE b/src/pip/_vendor/rich/LICENSE
new file mode 100644
index 000000000..441550556
--- /dev/null
+++ b/src/pip/_vendor/rich/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2020 Will McGugan
+
+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.
diff --git a/src/pip/_vendor/rich/__init__.py b/src/pip/_vendor/rich/__init__.py
new file mode 100644
index 000000000..73f58d774
--- /dev/null
+++ b/src/pip/_vendor/rich/__init__.py
@@ -0,0 +1,177 @@
+"""Rich text and beautiful formatting in the terminal."""
+
+import os
+from typing import IO, TYPE_CHECKING, Any, Callable, Optional, Union
+
+from ._extension import load_ipython_extension # noqa: F401
+
+__all__ = ["get_console", "reconfigure", "print", "inspect", "print_json"]
+
+if TYPE_CHECKING:
+ from .console import Console
+
+# Global console used by alternative print
+_console: Optional["Console"] = None
+
+try:
+ _IMPORT_CWD = os.path.abspath(os.getcwd())
+except FileNotFoundError:
+ # Can happen if the cwd has been deleted
+ _IMPORT_CWD = ""
+
+
+def get_console() -> "Console":
+ """Get a global :class:`~rich.console.Console` instance. This function is used when Rich requires a Console,
+ and hasn't been explicitly given one.
+
+ Returns:
+ Console: A console instance.
+ """
+ global _console
+ if _console is None:
+ from .console import Console
+
+ _console = Console()
+
+ return _console
+
+
+def reconfigure(*args: Any, **kwargs: Any) -> None:
+ """Reconfigures the global console by replacing it with another.
+
+ Args:
+ *args (Any): Positional arguments for the replacement :class:`~rich.console.Console`.
+ **kwargs (Any): Keyword arguments for the replacement :class:`~rich.console.Console`.
+ """
+ from pip._vendor.rich.console import Console
+
+ new_console = Console(*args, **kwargs)
+ _console = get_console()
+ _console.__dict__ = new_console.__dict__
+
+
+def print(
+ *objects: Any,
+ sep: str = " ",
+ end: str = "\n",
+ file: Optional[IO[str]] = None,
+ flush: bool = False,
+) -> None:
+ r"""Print object(s) supplied via positional arguments.
+ This function has an identical signature to the built-in print.
+ For more advanced features, see the :class:`~rich.console.Console` class.
+
+ Args:
+ sep (str, optional): Separator between printed objects. Defaults to " ".
+ end (str, optional): Character to write at end of output. Defaults to "\\n".
+ file (IO[str], optional): File to write to, or None for stdout. Defaults to None.
+ flush (bool, optional): Has no effect as Rich always flushes output. Defaults to False.
+
+ """
+ from .console import Console
+
+ write_console = get_console() if file is None else Console(file=file)
+ return write_console.print(*objects, sep=sep, end=end)
+
+
+def print_json(
+ json: Optional[str] = None,
+ *,
+ data: Any = None,
+ indent: Union[None, int, str] = 2,
+ highlight: bool = True,
+ skip_keys: bool = False,
+ ensure_ascii: bool = False,
+ check_circular: bool = True,
+ allow_nan: bool = True,
+ default: Optional[Callable[[Any], Any]] = None,
+ sort_keys: bool = False,
+) -> None:
+ """Pretty prints JSON. Output will be valid JSON.
+
+ Args:
+ json (str): A string containing JSON.
+ data (Any): If json is not supplied, then encode this data.
+ indent (int, optional): Number of spaces to indent. Defaults to 2.
+ highlight (bool, optional): Enable highlighting of output: Defaults to True.
+ skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False.
+ ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False.
+ check_circular (bool, optional): Check for circular references. Defaults to True.
+ allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True.
+ default (Callable, optional): A callable that converts values that can not be encoded
+ in to something that can be JSON encoded. Defaults to None.
+ sort_keys (bool, optional): Sort dictionary keys. Defaults to False.
+ """
+
+ get_console().print_json(
+ json,
+ data=data,
+ indent=indent,
+ highlight=highlight,
+ skip_keys=skip_keys,
+ ensure_ascii=ensure_ascii,
+ check_circular=check_circular,
+ allow_nan=allow_nan,
+ default=default,
+ sort_keys=sort_keys,
+ )
+
+
+def inspect(
+ obj: Any,
+ *,
+ console: Optional["Console"] = None,
+ title: Optional[str] = None,
+ help: bool = False,
+ methods: bool = False,
+ docs: bool = True,
+ private: bool = False,
+ dunder: bool = False,
+ sort: bool = True,
+ all: bool = False,
+ value: bool = True,
+) -> None:
+ """Inspect any Python object.
+
+ * inspect(<OBJECT>) to see summarized info.
+ * inspect(<OBJECT>, methods=True) to see methods.
+ * inspect(<OBJECT>, help=True) to see full (non-abbreviated) help.
+ * inspect(<OBJECT>, private=True) to see private attributes (single underscore).
+ * inspect(<OBJECT>, dunder=True) to see attributes beginning with double underscore.
+ * inspect(<OBJECT>, all=True) to see all attributes.
+
+ Args:
+ obj (Any): An object to inspect.
+ title (str, optional): Title to display over inspect result, or None use type. Defaults to None.
+ help (bool, optional): Show full help text rather than just first paragraph. Defaults to False.
+ methods (bool, optional): Enable inspection of callables. Defaults to False.
+ docs (bool, optional): Also render doc strings. Defaults to True.
+ private (bool, optional): Show private attributes (beginning with underscore). Defaults to False.
+ dunder (bool, optional): Show attributes starting with double underscore. Defaults to False.
+ sort (bool, optional): Sort attributes alphabetically. Defaults to True.
+ all (bool, optional): Show all attributes. Defaults to False.
+ value (bool, optional): Pretty print value. Defaults to True.
+ """
+ _console = console or get_console()
+ from pip._vendor.rich._inspect import Inspect
+
+ # Special case for inspect(inspect)
+ is_inspect = obj is inspect
+
+ _inspect = Inspect(
+ obj,
+ title=title,
+ help=is_inspect or help,
+ methods=is_inspect or methods,
+ docs=is_inspect or docs,
+ private=private,
+ dunder=dunder,
+ sort=sort,
+ all=all,
+ value=value,
+ )
+ _console.print(_inspect)
+
+
+if __name__ == "__main__": # pragma: no cover
+ print("Hello, **World**")
diff --git a/src/pip/_vendor/rich/__main__.py b/src/pip/_vendor/rich/__main__.py
new file mode 100644
index 000000000..270629fd8
--- /dev/null
+++ b/src/pip/_vendor/rich/__main__.py
@@ -0,0 +1,274 @@
+import colorsys
+import io
+from time import process_time
+
+from pip._vendor.rich import box
+from pip._vendor.rich.color import Color
+from pip._vendor.rich.console import Console, ConsoleOptions, Group, RenderableType, RenderResult
+from pip._vendor.rich.markdown import Markdown
+from pip._vendor.rich.measure import Measurement
+from pip._vendor.rich.pretty import Pretty
+from pip._vendor.rich.segment import Segment
+from pip._vendor.rich.style import Style
+from pip._vendor.rich.syntax import Syntax
+from pip._vendor.rich.table import Table
+from pip._vendor.rich.text import Text
+
+
+class ColorBox:
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ for y in range(0, 5):
+ for x in range(options.max_width):
+ h = x / options.max_width
+ l = 0.1 + ((y / 5) * 0.7)
+ r1, g1, b1 = colorsys.hls_to_rgb(h, l, 1.0)
+ r2, g2, b2 = colorsys.hls_to_rgb(h, l + 0.7 / 10, 1.0)
+ bgcolor = Color.from_rgb(r1 * 255, g1 * 255, b1 * 255)
+ color = Color.from_rgb(r2 * 255, g2 * 255, b2 * 255)
+ yield Segment("▄", Style(color=color, bgcolor=bgcolor))
+ yield Segment.line()
+
+ def __rich_measure__(
+ self, console: "Console", options: ConsoleOptions
+ ) -> Measurement:
+ return Measurement(1, options.max_width)
+
+
+def make_test_card() -> Table:
+ """Get a renderable that demonstrates a number of features."""
+ table = Table.grid(padding=1, pad_edge=True)
+ table.title = "Rich features"
+ table.add_column("Feature", no_wrap=True, justify="center", style="bold red")
+ table.add_column("Demonstration")
+
+ color_table = Table(
+ box=None,
+ expand=False,
+ show_header=False,
+ show_edge=False,
+ pad_edge=False,
+ )
+ color_table.add_row(
+ (
+ "✓ [bold green]4-bit color[/]\n"
+ "✓ [bold blue]8-bit color[/]\n"
+ "✓ [bold magenta]Truecolor (16.7 million)[/]\n"
+ "✓ [bold yellow]Dumb terminals[/]\n"
+ "✓ [bold cyan]Automatic color conversion"
+ ),
+ ColorBox(),
+ )
+
+ table.add_row("Colors", color_table)
+
+ table.add_row(
+ "Styles",
+ "All ansi styles: [bold]bold[/], [dim]dim[/], [italic]italic[/italic], [underline]underline[/], [strike]strikethrough[/], [reverse]reverse[/], and even [blink]blink[/].",
+ )
+
+ lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque in metus sed sapien ultricies pretium a at justo. Maecenas luctus velit et auctor maximus."
+ lorem_table = Table.grid(padding=1, collapse_padding=True)
+ lorem_table.pad_edge = False
+ lorem_table.add_row(
+ Text(lorem, justify="left", style="green"),
+ Text(lorem, justify="center", style="yellow"),
+ Text(lorem, justify="right", style="blue"),
+ Text(lorem, justify="full", style="red"),
+ )
+ table.add_row(
+ "Text",
+ Group(
+ Text.from_markup(
+ """Word wrap text. Justify [green]left[/], [yellow]center[/], [blue]right[/] or [red]full[/].\n"""
+ ),
+ lorem_table,
+ ),
+ )
+
+ def comparison(renderable1: RenderableType, renderable2: RenderableType) -> Table:
+ table = Table(show_header=False, pad_edge=False, box=None, expand=True)
+ table.add_column("1", ratio=1)
+ table.add_column("2", ratio=1)
+ table.add_row(renderable1, renderable2)
+ return table
+
+ table.add_row(
+ "Asian\nlanguage\nsupport",
+ ":flag_for_china: 该库支持中文,日文和韩文文本!\n:flag_for_japan: ライブラリは中国語、日本語、韓国語のテキストをサポートしています\n:flag_for_south_korea: 이 라이브러리는 중국어, 일본어 및 한국어 텍스트를 지원합니다",
+ )
+
+ markup_example = (
+ "[bold magenta]Rich[/] supports a simple [i]bbcode[/i]-like [b]markup[/b] for [yellow]color[/], [underline]style[/], and emoji! "
+ ":+1: :apple: :ant: :bear: :baguette_bread: :bus: "
+ )
+ table.add_row("Markup", markup_example)
+
+ example_table = Table(
+ show_edge=False,
+ show_header=True,
+ expand=False,
+ row_styles=["none", "dim"],
+ box=box.SIMPLE,
+ )
+ example_table.add_column("[green]Date", style="green", no_wrap=True)
+ example_table.add_column("[blue]Title", style="blue")
+ example_table.add_column(
+ "[cyan]Production Budget",
+ style="cyan",
+ justify="right",
+ no_wrap=True,
+ )
+ example_table.add_column(
+ "[magenta]Box Office",
+ style="magenta",
+ justify="right",
+ no_wrap=True,
+ )
+ example_table.add_row(
+ "Dec 20, 2019",
+ "Star Wars: The Rise of Skywalker",
+ "$275,000,000",
+ "$375,126,118",
+ )
+ example_table.add_row(
+ "May 25, 2018",
+ "[b]Solo[/]: A Star Wars Story",
+ "$275,000,000",
+ "$393,151,347",
+ )
+ example_table.add_row(
+ "Dec 15, 2017",
+ "Star Wars Ep. VIII: The Last Jedi",
+ "$262,000,000",
+ "[bold]$1,332,539,889[/bold]",
+ )
+ example_table.add_row(
+ "May 19, 1999",
+ "Star Wars Ep. [b]I[/b]: [i]The phantom Menace",
+ "$115,000,000",
+ "$1,027,044,677",
+ )
+
+ table.add_row("Tables", example_table)
+
+ code = '''\
+def iter_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
+ """Iterate and generate a tuple with a flag for last value."""
+ iter_values = iter(values)
+ try:
+ previous_value = next(iter_values)
+ except StopIteration:
+ return
+ for value in iter_values:
+ yield False, previous_value
+ previous_value = value
+ yield True, previous_value'''
+
+ pretty_data = {
+ "foo": [
+ 3.1427,
+ (
+ "Paul Atreides",
+ "Vladimir Harkonnen",
+ "Thufir Hawat",
+ ),
+ ],
+ "atomic": (False, True, None),
+ }
+ table.add_row(
+ "Syntax\nhighlighting\n&\npretty\nprinting",
+ comparison(
+ Syntax(code, "python3", line_numbers=True, indent_guides=True),
+ Pretty(pretty_data, indent_guides=True),
+ ),
+ )
+
+ markdown_example = """\
+# Markdown
+
+Supports much of the *markdown* __syntax__!
+
+- Headers
+- Basic formatting: **bold**, *italic*, `code`
+- Block quotes
+- Lists, and more...
+ """
+ table.add_row(
+ "Markdown", comparison("[cyan]" + markdown_example, Markdown(markdown_example))
+ )
+
+ table.add_row(
+ "+more!",
+ """Progress bars, columns, styled logging handler, tracebacks, etc...""",
+ )
+ return table
+
+
+if __name__ == "__main__": # pragma: no cover
+
+ console = Console(
+ file=io.StringIO(),
+ force_terminal=True,
+ )
+ test_card = make_test_card()
+
+ # Print once to warm cache
+ start = process_time()
+ console.print(test_card)
+ pre_cache_taken = round((process_time() - start) * 1000.0, 1)
+
+ console.file = io.StringIO()
+
+ start = process_time()
+ console.print(test_card)
+ taken = round((process_time() - start) * 1000.0, 1)
+
+ c = Console(record=True)
+ c.print(test_card)
+
+ print(f"rendered in {pre_cache_taken}ms (cold cache)")
+ print(f"rendered in {taken}ms (warm cache)")
+
+ from pip._vendor.rich.panel import Panel
+
+ console = Console()
+
+ sponsor_message = Table.grid(padding=1)
+ sponsor_message.add_column(style="green", justify="right")
+ sponsor_message.add_column(no_wrap=True)
+
+ sponsor_message.add_row(
+ "Textualize",
+ "[u blue link=https://github.com/textualize]https://github.com/textualize",
+ )
+ sponsor_message.add_row(
+ "Twitter",
+ "[u blue link=https://twitter.com/willmcgugan]https://twitter.com/willmcgugan",
+ )
+
+ intro_message = Text.from_markup(
+ """\
+We hope you enjoy using Rich!
+
+Rich is maintained with [red]:heart:[/] by [link=https://www.textualize.io]Textualize.io[/]
+
+- Will McGugan"""
+ )
+
+ message = Table.grid(padding=2)
+ message.add_column()
+ message.add_column(no_wrap=True)
+ message.add_row(intro_message, sponsor_message)
+
+ console.print(
+ Panel.fit(
+ message,
+ box=box.ROUNDED,
+ padding=(1, 2),
+ title="[b red]Thanks for trying out Rich!",
+ border_style="bright_blue",
+ ),
+ justify="center",
+ )
diff --git a/src/pip/_vendor/rich/_cell_widths.py b/src/pip/_vendor/rich/_cell_widths.py
new file mode 100644
index 000000000..36286df37
--- /dev/null
+++ b/src/pip/_vendor/rich/_cell_widths.py
@@ -0,0 +1,451 @@
+# Auto generated by make_terminal_widths.py
+
+CELL_WIDTHS = [
+ (0, 0, 0),
+ (1, 31, -1),
+ (127, 159, -1),
+ (768, 879, 0),
+ (1155, 1161, 0),
+ (1425, 1469, 0),
+ (1471, 1471, 0),
+ (1473, 1474, 0),
+ (1476, 1477, 0),
+ (1479, 1479, 0),
+ (1552, 1562, 0),
+ (1611, 1631, 0),
+ (1648, 1648, 0),
+ (1750, 1756, 0),
+ (1759, 1764, 0),
+ (1767, 1768, 0),
+ (1770, 1773, 0),
+ (1809, 1809, 0),
+ (1840, 1866, 0),
+ (1958, 1968, 0),
+ (2027, 2035, 0),
+ (2045, 2045, 0),
+ (2070, 2073, 0),
+ (2075, 2083, 0),
+ (2085, 2087, 0),
+ (2089, 2093, 0),
+ (2137, 2139, 0),
+ (2259, 2273, 0),
+ (2275, 2306, 0),
+ (2362, 2362, 0),
+ (2364, 2364, 0),
+ (2369, 2376, 0),
+ (2381, 2381, 0),
+ (2385, 2391, 0),
+ (2402, 2403, 0),
+ (2433, 2433, 0),
+ (2492, 2492, 0),
+ (2497, 2500, 0),
+ (2509, 2509, 0),
+ (2530, 2531, 0),
+ (2558, 2558, 0),
+ (2561, 2562, 0),
+ (2620, 2620, 0),
+ (2625, 2626, 0),
+ (2631, 2632, 0),
+ (2635, 2637, 0),
+ (2641, 2641, 0),
+ (2672, 2673, 0),
+ (2677, 2677, 0),
+ (2689, 2690, 0),
+ (2748, 2748, 0),
+ (2753, 2757, 0),
+ (2759, 2760, 0),
+ (2765, 2765, 0),
+ (2786, 2787, 0),
+ (2810, 2815, 0),
+ (2817, 2817, 0),
+ (2876, 2876, 0),
+ (2879, 2879, 0),
+ (2881, 2884, 0),
+ (2893, 2893, 0),
+ (2901, 2902, 0),
+ (2914, 2915, 0),
+ (2946, 2946, 0),
+ (3008, 3008, 0),
+ (3021, 3021, 0),
+ (3072, 3072, 0),
+ (3076, 3076, 0),
+ (3134, 3136, 0),
+ (3142, 3144, 0),
+ (3146, 3149, 0),
+ (3157, 3158, 0),
+ (3170, 3171, 0),
+ (3201, 3201, 0),
+ (3260, 3260, 0),
+ (3263, 3263, 0),
+ (3270, 3270, 0),
+ (3276, 3277, 0),
+ (3298, 3299, 0),
+ (3328, 3329, 0),
+ (3387, 3388, 0),
+ (3393, 3396, 0),
+ (3405, 3405, 0),
+ (3426, 3427, 0),
+ (3457, 3457, 0),
+ (3530, 3530, 0),
+ (3538, 3540, 0),
+ (3542, 3542, 0),
+ (3633, 3633, 0),
+ (3636, 3642, 0),
+ (3655, 3662, 0),
+ (3761, 3761, 0),
+ (3764, 3772, 0),
+ (3784, 3789, 0),
+ (3864, 3865, 0),
+ (3893, 3893, 0),
+ (3895, 3895, 0),
+ (3897, 3897, 0),
+ (3953, 3966, 0),
+ (3968, 3972, 0),
+ (3974, 3975, 0),
+ (3981, 3991, 0),
+ (3993, 4028, 0),
+ (4038, 4038, 0),
+ (4141, 4144, 0),
+ (4146, 4151, 0),
+ (4153, 4154, 0),
+ (4157, 4158, 0),
+ (4184, 4185, 0),
+ (4190, 4192, 0),
+ (4209, 4212, 0),
+ (4226, 4226, 0),
+ (4229, 4230, 0),
+ (4237, 4237, 0),
+ (4253, 4253, 0),
+ (4352, 4447, 2),
+ (4957, 4959, 0),
+ (5906, 5908, 0),
+ (5938, 5940, 0),
+ (5970, 5971, 0),
+ (6002, 6003, 0),
+ (6068, 6069, 0),
+ (6071, 6077, 0),
+ (6086, 6086, 0),
+ (6089, 6099, 0),
+ (6109, 6109, 0),
+ (6155, 6157, 0),
+ (6277, 6278, 0),
+ (6313, 6313, 0),
+ (6432, 6434, 0),
+ (6439, 6440, 0),
+ (6450, 6450, 0),
+ (6457, 6459, 0),
+ (6679, 6680, 0),
+ (6683, 6683, 0),
+ (6742, 6742, 0),
+ (6744, 6750, 0),
+ (6752, 6752, 0),
+ (6754, 6754, 0),
+ (6757, 6764, 0),
+ (6771, 6780, 0),
+ (6783, 6783, 0),
+ (6832, 6848, 0),
+ (6912, 6915, 0),
+ (6964, 6964, 0),
+ (6966, 6970, 0),
+ (6972, 6972, 0),
+ (6978, 6978, 0),
+ (7019, 7027, 0),
+ (7040, 7041, 0),
+ (7074, 7077, 0),
+ (7080, 7081, 0),
+ (7083, 7085, 0),
+ (7142, 7142, 0),
+ (7144, 7145, 0),
+ (7149, 7149, 0),
+ (7151, 7153, 0),
+ (7212, 7219, 0),
+ (7222, 7223, 0),
+ (7376, 7378, 0),
+ (7380, 7392, 0),
+ (7394, 7400, 0),
+ (7405, 7405, 0),
+ (7412, 7412, 0),
+ (7416, 7417, 0),
+ (7616, 7673, 0),
+ (7675, 7679, 0),
+ (8203, 8207, 0),
+ (8232, 8238, 0),
+ (8288, 8291, 0),
+ (8400, 8432, 0),
+ (8986, 8987, 2),
+ (9001, 9002, 2),
+ (9193, 9196, 2),
+ (9200, 9200, 2),
+ (9203, 9203, 2),
+ (9725, 9726, 2),
+ (9748, 9749, 2),
+ (9800, 9811, 2),
+ (9855, 9855, 2),
+ (9875, 9875, 2),
+ (9889, 9889, 2),
+ (9898, 9899, 2),
+ (9917, 9918, 2),
+ (9924, 9925, 2),
+ (9934, 9934, 2),
+ (9940, 9940, 2),
+ (9962, 9962, 2),
+ (9970, 9971, 2),
+ (9973, 9973, 2),
+ (9978, 9978, 2),
+ (9981, 9981, 2),
+ (9989, 9989, 2),
+ (9994, 9995, 2),
+ (10024, 10024, 2),
+ (10060, 10060, 2),
+ (10062, 10062, 2),
+ (10067, 10069, 2),
+ (10071, 10071, 2),
+ (10133, 10135, 2),
+ (10160, 10160, 2),
+ (10175, 10175, 2),
+ (11035, 11036, 2),
+ (11088, 11088, 2),
+ (11093, 11093, 2),
+ (11503, 11505, 0),
+ (11647, 11647, 0),
+ (11744, 11775, 0),
+ (11904, 11929, 2),
+ (11931, 12019, 2),
+ (12032, 12245, 2),
+ (12272, 12283, 2),
+ (12288, 12329, 2),
+ (12330, 12333, 0),
+ (12334, 12350, 2),
+ (12353, 12438, 2),
+ (12441, 12442, 0),
+ (12443, 12543, 2),
+ (12549, 12591, 2),
+ (12593, 12686, 2),
+ (12688, 12771, 2),
+ (12784, 12830, 2),
+ (12832, 12871, 2),
+ (12880, 19903, 2),
+ (19968, 42124, 2),
+ (42128, 42182, 2),
+ (42607, 42610, 0),
+ (42612, 42621, 0),
+ (42654, 42655, 0),
+ (42736, 42737, 0),
+ (43010, 43010, 0),
+ (43014, 43014, 0),
+ (43019, 43019, 0),
+ (43045, 43046, 0),
+ (43052, 43052, 0),
+ (43204, 43205, 0),
+ (43232, 43249, 0),
+ (43263, 43263, 0),
+ (43302, 43309, 0),
+ (43335, 43345, 0),
+ (43360, 43388, 2),
+ (43392, 43394, 0),
+ (43443, 43443, 0),
+ (43446, 43449, 0),
+ (43452, 43453, 0),
+ (43493, 43493, 0),
+ (43561, 43566, 0),
+ (43569, 43570, 0),
+ (43573, 43574, 0),
+ (43587, 43587, 0),
+ (43596, 43596, 0),
+ (43644, 43644, 0),
+ (43696, 43696, 0),
+ (43698, 43700, 0),
+ (43703, 43704, 0),
+ (43710, 43711, 0),
+ (43713, 43713, 0),
+ (43756, 43757, 0),
+ (43766, 43766, 0),
+ (44005, 44005, 0),
+ (44008, 44008, 0),
+ (44013, 44013, 0),
+ (44032, 55203, 2),
+ (63744, 64255, 2),
+ (64286, 64286, 0),
+ (65024, 65039, 0),
+ (65040, 65049, 2),
+ (65056, 65071, 0),
+ (65072, 65106, 2),
+ (65108, 65126, 2),
+ (65128, 65131, 2),
+ (65281, 65376, 2),
+ (65504, 65510, 2),
+ (66045, 66045, 0),
+ (66272, 66272, 0),
+ (66422, 66426, 0),
+ (68097, 68099, 0),
+ (68101, 68102, 0),
+ (68108, 68111, 0),
+ (68152, 68154, 0),
+ (68159, 68159, 0),
+ (68325, 68326, 0),
+ (68900, 68903, 0),
+ (69291, 69292, 0),
+ (69446, 69456, 0),
+ (69633, 69633, 0),
+ (69688, 69702, 0),
+ (69759, 69761, 0),
+ (69811, 69814, 0),
+ (69817, 69818, 0),
+ (69888, 69890, 0),
+ (69927, 69931, 0),
+ (69933, 69940, 0),
+ (70003, 70003, 0),
+ (70016, 70017, 0),
+ (70070, 70078, 0),
+ (70089, 70092, 0),
+ (70095, 70095, 0),
+ (70191, 70193, 0),
+ (70196, 70196, 0),
+ (70198, 70199, 0),
+ (70206, 70206, 0),
+ (70367, 70367, 0),
+ (70371, 70378, 0),
+ (70400, 70401, 0),
+ (70459, 70460, 0),
+ (70464, 70464, 0),
+ (70502, 70508, 0),
+ (70512, 70516, 0),
+ (70712, 70719, 0),
+ (70722, 70724, 0),
+ (70726, 70726, 0),
+ (70750, 70750, 0),
+ (70835, 70840, 0),
+ (70842, 70842, 0),
+ (70847, 70848, 0),
+ (70850, 70851, 0),
+ (71090, 71093, 0),
+ (71100, 71101, 0),
+ (71103, 71104, 0),
+ (71132, 71133, 0),
+ (71219, 71226, 0),
+ (71229, 71229, 0),
+ (71231, 71232, 0),
+ (71339, 71339, 0),
+ (71341, 71341, 0),
+ (71344, 71349, 0),
+ (71351, 71351, 0),
+ (71453, 71455, 0),
+ (71458, 71461, 0),
+ (71463, 71467, 0),
+ (71727, 71735, 0),
+ (71737, 71738, 0),
+ (71995, 71996, 0),
+ (71998, 71998, 0),
+ (72003, 72003, 0),
+ (72148, 72151, 0),
+ (72154, 72155, 0),
+ (72160, 72160, 0),
+ (72193, 72202, 0),
+ (72243, 72248, 0),
+ (72251, 72254, 0),
+ (72263, 72263, 0),
+ (72273, 72278, 0),
+ (72281, 72283, 0),
+ (72330, 72342, 0),
+ (72344, 72345, 0),
+ (72752, 72758, 0),
+ (72760, 72765, 0),
+ (72767, 72767, 0),
+ (72850, 72871, 0),
+ (72874, 72880, 0),
+ (72882, 72883, 0),
+ (72885, 72886, 0),
+ (73009, 73014, 0),
+ (73018, 73018, 0),
+ (73020, 73021, 0),
+ (73023, 73029, 0),
+ (73031, 73031, 0),
+ (73104, 73105, 0),
+ (73109, 73109, 0),
+ (73111, 73111, 0),
+ (73459, 73460, 0),
+ (92912, 92916, 0),
+ (92976, 92982, 0),
+ (94031, 94031, 0),
+ (94095, 94098, 0),
+ (94176, 94179, 2),
+ (94180, 94180, 0),
+ (94192, 94193, 2),
+ (94208, 100343, 2),
+ (100352, 101589, 2),
+ (101632, 101640, 2),
+ (110592, 110878, 2),
+ (110928, 110930, 2),
+ (110948, 110951, 2),
+ (110960, 111355, 2),
+ (113821, 113822, 0),
+ (119143, 119145, 0),
+ (119163, 119170, 0),
+ (119173, 119179, 0),
+ (119210, 119213, 0),
+ (119362, 119364, 0),
+ (121344, 121398, 0),
+ (121403, 121452, 0),
+ (121461, 121461, 0),
+ (121476, 121476, 0),
+ (121499, 121503, 0),
+ (121505, 121519, 0),
+ (122880, 122886, 0),
+ (122888, 122904, 0),
+ (122907, 122913, 0),
+ (122915, 122916, 0),
+ (122918, 122922, 0),
+ (123184, 123190, 0),
+ (123628, 123631, 0),
+ (125136, 125142, 0),
+ (125252, 125258, 0),
+ (126980, 126980, 2),
+ (127183, 127183, 2),
+ (127374, 127374, 2),
+ (127377, 127386, 2),
+ (127488, 127490, 2),
+ (127504, 127547, 2),
+ (127552, 127560, 2),
+ (127568, 127569, 2),
+ (127584, 127589, 2),
+ (127744, 127776, 2),
+ (127789, 127797, 2),
+ (127799, 127868, 2),
+ (127870, 127891, 2),
+ (127904, 127946, 2),
+ (127951, 127955, 2),
+ (127968, 127984, 2),
+ (127988, 127988, 2),
+ (127992, 128062, 2),
+ (128064, 128064, 2),
+ (128066, 128252, 2),
+ (128255, 128317, 2),
+ (128331, 128334, 2),
+ (128336, 128359, 2),
+ (128378, 128378, 2),
+ (128405, 128406, 2),
+ (128420, 128420, 2),
+ (128507, 128591, 2),
+ (128640, 128709, 2),
+ (128716, 128716, 2),
+ (128720, 128722, 2),
+ (128725, 128727, 2),
+ (128747, 128748, 2),
+ (128756, 128764, 2),
+ (128992, 129003, 2),
+ (129292, 129338, 2),
+ (129340, 129349, 2),
+ (129351, 129400, 2),
+ (129402, 129483, 2),
+ (129485, 129535, 2),
+ (129648, 129652, 2),
+ (129656, 129658, 2),
+ (129664, 129670, 2),
+ (129680, 129704, 2),
+ (129712, 129718, 2),
+ (129728, 129730, 2),
+ (129744, 129750, 2),
+ (131072, 196605, 2),
+ (196608, 262141, 2),
+ (917760, 917999, 0),
+]
diff --git a/src/pip/_vendor/rich/_emoji_codes.py b/src/pip/_vendor/rich/_emoji_codes.py
new file mode 100644
index 000000000..1f2877bb2
--- /dev/null
+++ b/src/pip/_vendor/rich/_emoji_codes.py
@@ -0,0 +1,3610 @@
+EMOJI = {
+ "1st_place_medal": "🥇",
+ "2nd_place_medal": "🥈",
+ "3rd_place_medal": "🥉",
+ "ab_button_(blood_type)": "🆎",
+ "atm_sign": "🏧",
+ "a_button_(blood_type)": "🅰",
+ "afghanistan": "🇦🇫",
+ "albania": "🇦🇱",
+ "algeria": "🇩🇿",
+ "american_samoa": "🇦🇸",
+ "andorra": "🇦🇩",
+ "angola": "🇦🇴",
+ "anguilla": "🇦🇮",
+ "antarctica": "🇦🇶",
+ "antigua_&_barbuda": "🇦🇬",
+ "aquarius": "♒",
+ "argentina": "🇦🇷",
+ "aries": "♈",
+ "armenia": "🇦🇲",
+ "aruba": "🇦🇼",
+ "ascension_island": "🇦🇨",
+ "australia": "🇦🇺",
+ "austria": "🇦🇹",
+ "azerbaijan": "🇦🇿",
+ "back_arrow": "🔙",
+ "b_button_(blood_type)": "🅱",
+ "bahamas": "🇧🇸",
+ "bahrain": "🇧🇭",
+ "bangladesh": "🇧🇩",
+ "barbados": "🇧🇧",
+ "belarus": "🇧🇾",
+ "belgium": "🇧🇪",
+ "belize": "🇧🇿",
+ "benin": "🇧🇯",
+ "bermuda": "🇧🇲",
+ "bhutan": "🇧🇹",
+ "bolivia": "🇧🇴",
+ "bosnia_&_herzegovina": "🇧🇦",
+ "botswana": "🇧🇼",
+ "bouvet_island": "🇧🇻",
+ "brazil": "🇧🇷",
+ "british_indian_ocean_territory": "🇮🇴",
+ "british_virgin_islands": "🇻🇬",
+ "brunei": "🇧🇳",
+ "bulgaria": "🇧🇬",
+ "burkina_faso": "🇧🇫",
+ "burundi": "🇧🇮",
+ "cl_button": "🆑",
+ "cool_button": "🆒",
+ "cambodia": "🇰🇭",
+ "cameroon": "🇨🇲",
+ "canada": "🇨🇦",
+ "canary_islands": "🇮🇨",
+ "cancer": "♋",
+ "cape_verde": "🇨🇻",
+ "capricorn": "♑",
+ "caribbean_netherlands": "🇧🇶",
+ "cayman_islands": "🇰🇾",
+ "central_african_republic": "🇨🇫",
+ "ceuta_&_melilla": "🇪🇦",
+ "chad": "🇹🇩",
+ "chile": "🇨🇱",
+ "china": "🇨🇳",
+ "christmas_island": "🇨🇽",
+ "christmas_tree": "🎄",
+ "clipperton_island": "🇨🇵",
+ "cocos_(keeling)_islands": "🇨🇨",
+ "colombia": "🇨🇴",
+ "comoros": "🇰🇲",
+ "congo_-_brazzaville": "🇨🇬",
+ "congo_-_kinshasa": "🇨🇩",
+ "cook_islands": "🇨🇰",
+ "costa_rica": "🇨🇷",
+ "croatia": "🇭🇷",
+ "cuba": "🇨🇺",
+ "curaçao": "🇨🇼",
+ "cyprus": "🇨🇾",
+ "czechia": "🇨🇿",
+ "côte_d’ivoire": "🇨🇮",
+ "denmark": "🇩🇰",
+ "diego_garcia": "🇩🇬",
+ "djibouti": "🇩🇯",
+ "dominica": "🇩🇲",
+ "dominican_republic": "🇩🇴",
+ "end_arrow": "🔚",
+ "ecuador": "🇪🇨",
+ "egypt": "🇪🇬",
+ "el_salvador": "🇸🇻",
+ "england": "🏴\U000e0067\U000e0062\U000e0065\U000e006e\U000e0067\U000e007f",
+ "equatorial_guinea": "🇬🇶",
+ "eritrea": "🇪🇷",
+ "estonia": "🇪🇪",
+ "ethiopia": "🇪🇹",
+ "european_union": "🇪🇺",
+ "free_button": "🆓",
+ "falkland_islands": "🇫🇰",
+ "faroe_islands": "🇫🇴",
+ "fiji": "🇫🇯",
+ "finland": "🇫🇮",
+ "france": "🇫🇷",
+ "french_guiana": "🇬🇫",
+ "french_polynesia": "🇵🇫",
+ "french_southern_territories": "🇹🇫",
+ "gabon": "🇬🇦",
+ "gambia": "🇬🇲",
+ "gemini": "♊",
+ "georgia": "🇬🇪",
+ "germany": "🇩🇪",
+ "ghana": "🇬🇭",
+ "gibraltar": "🇬🇮",
+ "greece": "🇬🇷",
+ "greenland": "🇬🇱",
+ "grenada": "🇬🇩",
+ "guadeloupe": "🇬🇵",
+ "guam": "🇬🇺",
+ "guatemala": "🇬🇹",
+ "guernsey": "🇬🇬",
+ "guinea": "🇬🇳",
+ "guinea-bissau": "🇬🇼",
+ "guyana": "🇬🇾",
+ "haiti": "🇭🇹",
+ "heard_&_mcdonald_islands": "🇭🇲",
+ "honduras": "🇭🇳",
+ "hong_kong_sar_china": "🇭🇰",
+ "hungary": "🇭🇺",
+ "id_button": "🆔",
+ "iceland": "🇮🇸",
+ "india": "🇮🇳",
+ "indonesia": "🇮🇩",
+ "iran": "🇮🇷",
+ "iraq": "🇮🇶",
+ "ireland": "🇮🇪",
+ "isle_of_man": "🇮🇲",
+ "israel": "🇮🇱",
+ "italy": "🇮🇹",
+ "jamaica": "🇯🇲",
+ "japan": "🗾",
+ "japanese_acceptable_button": "🉑",
+ "japanese_application_button": "🈸",
+ "japanese_bargain_button": "🉐",
+ "japanese_castle": "🏯",
+ "japanese_congratulations_button": "㊗",
+ "japanese_discount_button": "🈹",
+ "japanese_dolls": "🎎",
+ "japanese_free_of_charge_button": "🈚",
+ "japanese_here_button": "🈁",
+ "japanese_monthly_amount_button": "🈷",
+ "japanese_no_vacancy_button": "🈵",
+ "japanese_not_free_of_charge_button": "🈶",
+ "japanese_open_for_business_button": "🈺",
+ "japanese_passing_grade_button": "🈴",
+ "japanese_post_office": "🏣",
+ "japanese_prohibited_button": "🈲",
+ "japanese_reserved_button": "🈯",
+ "japanese_secret_button": "㊙",
+ "japanese_service_charge_button": "🈂",
+ "japanese_symbol_for_beginner": "🔰",
+ "japanese_vacancy_button": "🈳",
+ "jersey": "🇯🇪",
+ "jordan": "🇯🇴",
+ "kazakhstan": "🇰🇿",
+ "kenya": "🇰🇪",
+ "kiribati": "🇰🇮",
+ "kosovo": "🇽🇰",
+ "kuwait": "🇰🇼",
+ "kyrgyzstan": "🇰🇬",
+ "laos": "🇱🇦",
+ "latvia": "🇱🇻",
+ "lebanon": "🇱🇧",
+ "leo": "♌",
+ "lesotho": "🇱🇸",
+ "liberia": "🇱🇷",
+ "libra": "♎",
+ "libya": "🇱🇾",
+ "liechtenstein": "🇱🇮",
+ "lithuania": "🇱🇹",
+ "luxembourg": "🇱🇺",
+ "macau_sar_china": "🇲🇴",
+ "macedonia": "🇲🇰",
+ "madagascar": "🇲🇬",
+ "malawi": "🇲🇼",
+ "malaysia": "🇲🇾",
+ "maldives": "🇲🇻",
+ "mali": "🇲🇱",
+ "malta": "🇲🇹",
+ "marshall_islands": "🇲🇭",
+ "martinique": "🇲🇶",
+ "mauritania": "🇲🇷",
+ "mauritius": "🇲🇺",
+ "mayotte": "🇾🇹",
+ "mexico": "🇲🇽",
+ "micronesia": "🇫🇲",
+ "moldova": "🇲🇩",
+ "monaco": "🇲🇨",
+ "mongolia": "🇲🇳",
+ "montenegro": "🇲🇪",
+ "montserrat": "🇲🇸",
+ "morocco": "🇲🇦",
+ "mozambique": "🇲🇿",
+ "mrs._claus": "🤶",
+ "mrs._claus_dark_skin_tone": "🤶🏿",
+ "mrs._claus_light_skin_tone": "🤶🏻",
+ "mrs._claus_medium-dark_skin_tone": "🤶🏾",
+ "mrs._claus_medium-light_skin_tone": "🤶🏼",
+ "mrs._claus_medium_skin_tone": "🤶🏽",
+ "myanmar_(burma)": "🇲🇲",
+ "new_button": "🆕",
+ "ng_button": "🆖",
+ "namibia": "🇳🇦",
+ "nauru": "🇳🇷",
+ "nepal": "🇳🇵",
+ "netherlands": "🇳🇱",
+ "new_caledonia": "🇳🇨",
+ "new_zealand": "🇳🇿",
+ "nicaragua": "🇳🇮",
+ "niger": "🇳🇪",
+ "nigeria": "🇳🇬",
+ "niue": "🇳🇺",
+ "norfolk_island": "🇳🇫",
+ "north_korea": "🇰🇵",
+ "northern_mariana_islands": "🇲🇵",
+ "norway": "🇳🇴",
+ "ok_button": "🆗",
+ "ok_hand": "👌",
+ "ok_hand_dark_skin_tone": "👌🏿",
+ "ok_hand_light_skin_tone": "👌🏻",
+ "ok_hand_medium-dark_skin_tone": "👌🏾",
+ "ok_hand_medium-light_skin_tone": "👌🏼",
+ "ok_hand_medium_skin_tone": "👌🏽",
+ "on!_arrow": "🔛",
+ "o_button_(blood_type)": "🅾",
+ "oman": "🇴🇲",
+ "ophiuchus": "⛎",
+ "p_button": "🅿",
+ "pakistan": "🇵🇰",
+ "palau": "🇵🇼",
+ "palestinian_territories": "🇵🇸",
+ "panama": "🇵🇦",
+ "papua_new_guinea": "🇵🇬",
+ "paraguay": "🇵🇾",
+ "peru": "🇵🇪",
+ "philippines": "🇵🇭",
+ "pisces": "♓",
+ "pitcairn_islands": "🇵🇳",
+ "poland": "🇵🇱",
+ "portugal": "🇵🇹",
+ "puerto_rico": "🇵🇷",
+ "qatar": "🇶🇦",
+ "romania": "🇷🇴",
+ "russia": "🇷🇺",
+ "rwanda": "🇷🇼",
+ "réunion": "🇷🇪",
+ "soon_arrow": "🔜",
+ "sos_button": "🆘",
+ "sagittarius": "♐",
+ "samoa": "🇼🇸",
+ "san_marino": "🇸🇲",
+ "santa_claus": "🎅",
+ "santa_claus_dark_skin_tone": "🎅🏿",
+ "santa_claus_light_skin_tone": "🎅🏻",
+ "santa_claus_medium-dark_skin_tone": "🎅🏾",
+ "santa_claus_medium-light_skin_tone": "🎅🏼",
+ "santa_claus_medium_skin_tone": "🎅🏽",
+ "saudi_arabia": "🇸🇦",
+ "scorpio": "♏",
+ "scotland": "🏴\U000e0067\U000e0062\U000e0073\U000e0063\U000e0074\U000e007f",
+ "senegal": "🇸🇳",
+ "serbia": "🇷🇸",
+ "seychelles": "🇸🇨",
+ "sierra_leone": "🇸🇱",
+ "singapore": "🇸🇬",
+ "sint_maarten": "🇸🇽",
+ "slovakia": "🇸🇰",
+ "slovenia": "🇸🇮",
+ "solomon_islands": "🇸🇧",
+ "somalia": "🇸🇴",
+ "south_africa": "🇿🇦",
+ "south_georgia_&_south_sandwich_islands": "🇬🇸",
+ "south_korea": "🇰🇷",
+ "south_sudan": "🇸🇸",
+ "spain": "🇪🇸",
+ "sri_lanka": "🇱🇰",
+ "st._barthélemy": "🇧🇱",
+ "st._helena": "🇸🇭",
+ "st._kitts_&_nevis": "🇰🇳",
+ "st._lucia": "🇱🇨",
+ "st._martin": "🇲🇫",
+ "st._pierre_&_miquelon": "🇵🇲",
+ "st._vincent_&_grenadines": "🇻🇨",
+ "statue_of_liberty": "🗽",
+ "sudan": "🇸🇩",
+ "suriname": "🇸🇷",
+ "svalbard_&_jan_mayen": "🇸🇯",
+ "swaziland": "🇸🇿",
+ "sweden": "🇸🇪",
+ "switzerland": "🇨🇭",
+ "syria": "🇸🇾",
+ "são_tomé_&_príncipe": "🇸🇹",
+ "t-rex": "🦖",
+ "top_arrow": "🔝",
+ "taiwan": "🇹🇼",
+ "tajikistan": "🇹🇯",
+ "tanzania": "🇹🇿",
+ "taurus": "♉",
+ "thailand": "🇹🇭",
+ "timor-leste": "🇹🇱",
+ "togo": "🇹🇬",
+ "tokelau": "🇹🇰",
+ "tokyo_tower": "🗼",
+ "tonga": "🇹🇴",
+ "trinidad_&_tobago": "🇹🇹",
+ "tristan_da_cunha": "🇹🇦",
+ "tunisia": "🇹🇳",
+ "turkey": "🦃",
+ "turkmenistan": "🇹🇲",
+ "turks_&_caicos_islands": "🇹🇨",
+ "tuvalu": "🇹🇻",
+ "u.s._outlying_islands": "🇺🇲",
+ "u.s._virgin_islands": "🇻🇮",
+ "up!_button": "🆙",
+ "uganda": "🇺🇬",
+ "ukraine": "🇺🇦",
+ "united_arab_emirates": "🇦🇪",
+ "united_kingdom": "🇬🇧",
+ "united_nations": "🇺🇳",
+ "united_states": "🇺🇸",
+ "uruguay": "🇺🇾",
+ "uzbekistan": "🇺🇿",
+ "vs_button": "🆚",
+ "vanuatu": "🇻🇺",
+ "vatican_city": "🇻🇦",
+ "venezuela": "🇻🇪",
+ "vietnam": "🇻🇳",
+ "virgo": "♍",
+ "wales": "🏴\U000e0067\U000e0062\U000e0077\U000e006c\U000e0073\U000e007f",
+ "wallis_&_futuna": "🇼🇫",
+ "western_sahara": "🇪🇭",
+ "yemen": "🇾🇪",
+ "zambia": "🇿🇲",
+ "zimbabwe": "🇿🇼",
+ "abacus": "🧮",
+ "adhesive_bandage": "🩹",
+ "admission_tickets": "🎟",
+ "adult": "🧑",
+ "adult_dark_skin_tone": "🧑🏿",
+ "adult_light_skin_tone": "🧑🏻",
+ "adult_medium-dark_skin_tone": "🧑🏾",
+ "adult_medium-light_skin_tone": "🧑🏼",
+ "adult_medium_skin_tone": "🧑🏽",
+ "aerial_tramway": "🚡",
+ "airplane": "✈",
+ "airplane_arrival": "🛬",
+ "airplane_departure": "🛫",
+ "alarm_clock": "⏰",
+ "alembic": "⚗",
+ "alien": "👽",
+ "alien_monster": "👾",
+ "ambulance": "🚑",
+ "american_football": "🏈",
+ "amphora": "🏺",
+ "anchor": "⚓",
+ "anger_symbol": "💢",
+ "angry_face": "😠",
+ "angry_face_with_horns": "👿",
+ "anguished_face": "😧",
+ "ant": "🐜",
+ "antenna_bars": "📶",
+ "anxious_face_with_sweat": "😰",
+ "articulated_lorry": "🚛",
+ "artist_palette": "🎨",
+ "astonished_face": "😲",
+ "atom_symbol": "⚛",
+ "auto_rickshaw": "🛺",
+ "automobile": "🚗",
+ "avocado": "🥑",
+ "axe": "🪓",
+ "baby": "👶",
+ "baby_angel": "👼",
+ "baby_angel_dark_skin_tone": "👼🏿",
+ "baby_angel_light_skin_tone": "👼🏻",
+ "baby_angel_medium-dark_skin_tone": "👼🏾",
+ "baby_angel_medium-light_skin_tone": "👼🏼",
+ "baby_angel_medium_skin_tone": "👼🏽",
+ "baby_bottle": "🍼",
+ "baby_chick": "🐤",
+ "baby_dark_skin_tone": "👶🏿",
+ "baby_light_skin_tone": "👶🏻",
+ "baby_medium-dark_skin_tone": "👶🏾",
+ "baby_medium-light_skin_tone": "👶🏼",
+ "baby_medium_skin_tone": "👶🏽",
+ "baby_symbol": "🚼",
+ "backhand_index_pointing_down": "👇",
+ "backhand_index_pointing_down_dark_skin_tone": "👇🏿",
+ "backhand_index_pointing_down_light_skin_tone": "👇🏻",
+ "backhand_index_pointing_down_medium-dark_skin_tone": "👇🏾",
+ "backhand_index_pointing_down_medium-light_skin_tone": "👇🏼",
+ "backhand_index_pointing_down_medium_skin_tone": "👇🏽",
+ "backhand_index_pointing_left": "👈",
+ "backhand_index_pointing_left_dark_skin_tone": "👈🏿",
+ "backhand_index_pointing_left_light_skin_tone": "👈🏻",
+ "backhand_index_pointing_left_medium-dark_skin_tone": "👈🏾",
+ "backhand_index_pointing_left_medium-light_skin_tone": "👈🏼",
+ "backhand_index_pointing_left_medium_skin_tone": "👈🏽",
+ "backhand_index_pointing_right": "👉",
+ "backhand_index_pointing_right_dark_skin_tone": "👉🏿",
+ "backhand_index_pointing_right_light_skin_tone": "👉🏻",
+ "backhand_index_pointing_right_medium-dark_skin_tone": "👉🏾",
+ "backhand_index_pointing_right_medium-light_skin_tone": "👉🏼",
+ "backhand_index_pointing_right_medium_skin_tone": "👉🏽",
+ "backhand_index_pointing_up": "👆",
+ "backhand_index_pointing_up_dark_skin_tone": "👆🏿",
+ "backhand_index_pointing_up_light_skin_tone": "👆🏻",
+ "backhand_index_pointing_up_medium-dark_skin_tone": "👆🏾",
+ "backhand_index_pointing_up_medium-light_skin_tone": "👆🏼",
+ "backhand_index_pointing_up_medium_skin_tone": "👆🏽",
+ "bacon": "🥓",
+ "badger": "🦡",
+ "badminton": "🏸",
+ "bagel": "🥯",
+ "baggage_claim": "🛄",
+ "baguette_bread": "🥖",
+ "balance_scale": "⚖",
+ "bald": "🦲",
+ "bald_man": "👨\u200d🦲",
+ "bald_woman": "👩\u200d🦲",
+ "ballet_shoes": "🩰",
+ "balloon": "🎈",
+ "ballot_box_with_ballot": "🗳",
+ "ballot_box_with_check": "☑",
+ "banana": "🍌",
+ "banjo": "🪕",
+ "bank": "🏦",
+ "bar_chart": "📊",
+ "barber_pole": "💈",
+ "baseball": "⚾",
+ "basket": "🧺",
+ "basketball": "🏀",
+ "bat": "🦇",
+ "bathtub": "🛁",
+ "battery": "🔋",
+ "beach_with_umbrella": "🏖",
+ "beaming_face_with_smiling_eyes": "😁",
+ "bear_face": "🐻",
+ "bearded_person": "🧔",
+ "bearded_person_dark_skin_tone": "🧔🏿",
+ "bearded_person_light_skin_tone": "🧔🏻",
+ "bearded_person_medium-dark_skin_tone": "🧔🏾",
+ "bearded_person_medium-light_skin_tone": "🧔🏼",
+ "bearded_person_medium_skin_tone": "🧔🏽",
+ "beating_heart": "💓",
+ "bed": "🛏",
+ "beer_mug": "🍺",
+ "bell": "🔔",
+ "bell_with_slash": "🔕",
+ "bellhop_bell": "🛎",
+ "bento_box": "🍱",
+ "beverage_box": "🧃",
+ "bicycle": "🚲",
+ "bikini": "👙",
+ "billed_cap": "🧢",
+ "biohazard": "☣",
+ "bird": "🐦",
+ "birthday_cake": "🎂",
+ "black_circle": "⚫",
+ "black_flag": "🏴",
+ "black_heart": "🖤",
+ "black_large_square": "⬛",
+ "black_medium-small_square": "◾",
+ "black_medium_square": "◼",
+ "black_nib": "✒",
+ "black_small_square": "▪",
+ "black_square_button": "🔲",
+ "blond-haired_man": "👱\u200d♂️",
+ "blond-haired_man_dark_skin_tone": "👱🏿\u200d♂️",
+ "blond-haired_man_light_skin_tone": "👱🏻\u200d♂️",
+ "blond-haired_man_medium-dark_skin_tone": "👱🏾\u200d♂️",
+ "blond-haired_man_medium-light_skin_tone": "👱🏼\u200d♂️",
+ "blond-haired_man_medium_skin_tone": "👱🏽\u200d♂️",
+ "blond-haired_person": "👱",
+ "blond-haired_person_dark_skin_tone": "👱🏿",
+ "blond-haired_person_light_skin_tone": "👱🏻",
+ "blond-haired_person_medium-dark_skin_tone": "👱🏾",
+ "blond-haired_person_medium-light_skin_tone": "👱🏼",
+ "blond-haired_person_medium_skin_tone": "👱🏽",
+ "blond-haired_woman": "👱\u200d♀️",
+ "blond-haired_woman_dark_skin_tone": "👱🏿\u200d♀️",
+ "blond-haired_woman_light_skin_tone": "👱🏻\u200d♀️",
+ "blond-haired_woman_medium-dark_skin_tone": "👱🏾\u200d♀️",
+ "blond-haired_woman_medium-light_skin_tone": "👱🏼\u200d♀️",
+ "blond-haired_woman_medium_skin_tone": "👱🏽\u200d♀️",
+ "blossom": "🌼",
+ "blowfish": "🐡",
+ "blue_book": "📘",
+ "blue_circle": "🔵",
+ "blue_heart": "💙",
+ "blue_square": "🟦",
+ "boar": "🐗",
+ "bomb": "💣",
+ "bone": "🦴",
+ "bookmark": "🔖",
+ "bookmark_tabs": "📑",
+ "books": "📚",
+ "bottle_with_popping_cork": "🍾",
+ "bouquet": "💐",
+ "bow_and_arrow": "🏹",
+ "bowl_with_spoon": "🥣",
+ "bowling": "🎳",
+ "boxing_glove": "🥊",
+ "boy": "👦",
+ "boy_dark_skin_tone": "👦🏿",
+ "boy_light_skin_tone": "👦🏻",
+ "boy_medium-dark_skin_tone": "👦🏾",
+ "boy_medium-light_skin_tone": "👦🏼",
+ "boy_medium_skin_tone": "👦🏽",
+ "brain": "🧠",
+ "bread": "🍞",
+ "breast-feeding": "🤱",
+ "breast-feeding_dark_skin_tone": "🤱🏿",
+ "breast-feeding_light_skin_tone": "🤱🏻",
+ "breast-feeding_medium-dark_skin_tone": "🤱🏾",
+ "breast-feeding_medium-light_skin_tone": "🤱🏼",
+ "breast-feeding_medium_skin_tone": "🤱🏽",
+ "brick": "🧱",
+ "bride_with_veil": "👰",
+ "bride_with_veil_dark_skin_tone": "👰🏿",
+ "bride_with_veil_light_skin_tone": "👰🏻",
+ "bride_with_veil_medium-dark_skin_tone": "👰🏾",
+ "bride_with_veil_medium-light_skin_tone": "👰🏼",
+ "bride_with_veil_medium_skin_tone": "👰🏽",
+ "bridge_at_night": "🌉",
+ "briefcase": "💼",
+ "briefs": "🩲",
+ "bright_button": "🔆",
+ "broccoli": "🥦",
+ "broken_heart": "💔",
+ "broom": "🧹",
+ "brown_circle": "🟤",
+ "brown_heart": "🤎",
+ "brown_square": "🟫",
+ "bug": "🐛",
+ "building_construction": "🏗",
+ "bullet_train": "🚅",
+ "burrito": "🌯",
+ "bus": "🚌",
+ "bus_stop": "🚏",
+ "bust_in_silhouette": "👤",
+ "busts_in_silhouette": "👥",
+ "butter": "🧈",
+ "butterfly": "🦋",
+ "cactus": "🌵",
+ "calendar": "📆",
+ "call_me_hand": "🤙",
+ "call_me_hand_dark_skin_tone": "🤙🏿",
+ "call_me_hand_light_skin_tone": "🤙🏻",
+ "call_me_hand_medium-dark_skin_tone": "🤙🏾",
+ "call_me_hand_medium-light_skin_tone": "🤙🏼",
+ "call_me_hand_medium_skin_tone": "🤙🏽",
+ "camel": "🐫",
+ "camera": "📷",
+ "camera_with_flash": "📸",
+ "camping": "🏕",
+ "candle": "🕯",
+ "candy": "🍬",
+ "canned_food": "🥫",
+ "canoe": "🛶",
+ "card_file_box": "🗃",
+ "card_index": "📇",
+ "card_index_dividers": "🗂",
+ "carousel_horse": "🎠",
+ "carp_streamer": "🎏",
+ "carrot": "🥕",
+ "castle": "🏰",
+ "cat": "🐱",
+ "cat_face": "🐱",
+ "cat_face_with_tears_of_joy": "😹",
+ "cat_face_with_wry_smile": "😼",
+ "chains": "⛓",
+ "chair": "🪑",
+ "chart_decreasing": "📉",
+ "chart_increasing": "📈",
+ "chart_increasing_with_yen": "💹",
+ "cheese_wedge": "🧀",
+ "chequered_flag": "🏁",
+ "cherries": "🍒",
+ "cherry_blossom": "🌸",
+ "chess_pawn": "♟",
+ "chestnut": "🌰",
+ "chicken": "🐔",
+ "child": "🧒",
+ "child_dark_skin_tone": "🧒🏿",
+ "child_light_skin_tone": "🧒🏻",
+ "child_medium-dark_skin_tone": "🧒🏾",
+ "child_medium-light_skin_tone": "🧒🏼",
+ "child_medium_skin_tone": "🧒🏽",
+ "children_crossing": "🚸",
+ "chipmunk": "🐿",
+ "chocolate_bar": "🍫",
+ "chopsticks": "🥢",
+ "church": "⛪",
+ "cigarette": "🚬",
+ "cinema": "🎦",
+ "circled_m": "Ⓜ",
+ "circus_tent": "🎪",
+ "cityscape": "🏙",
+ "cityscape_at_dusk": "🌆",
+ "clamp": "🗜",
+ "clapper_board": "🎬",
+ "clapping_hands": "👏",
+ "clapping_hands_dark_skin_tone": "👏🏿",
+ "clapping_hands_light_skin_tone": "👏🏻",
+ "clapping_hands_medium-dark_skin_tone": "👏🏾",
+ "clapping_hands_medium-light_skin_tone": "👏🏼",
+ "clapping_hands_medium_skin_tone": "👏🏽",
+ "classical_building": "🏛",
+ "clinking_beer_mugs": "🍻",
+ "clinking_glasses": "🥂",
+ "clipboard": "📋",
+ "clockwise_vertical_arrows": "🔃",
+ "closed_book": "📕",
+ "closed_mailbox_with_lowered_flag": "📪",
+ "closed_mailbox_with_raised_flag": "📫",
+ "closed_umbrella": "🌂",
+ "cloud": "☁",
+ "cloud_with_lightning": "🌩",
+ "cloud_with_lightning_and_rain": "⛈",
+ "cloud_with_rain": "🌧",
+ "cloud_with_snow": "🌨",
+ "clown_face": "🤡",
+ "club_suit": "♣",
+ "clutch_bag": "👝",
+ "coat": "🧥",
+ "cocktail_glass": "🍸",
+ "coconut": "🥥",
+ "coffin": "⚰",
+ "cold_face": "🥶",
+ "collision": "💥",
+ "comet": "☄",
+ "compass": "🧭",
+ "computer_disk": "💽",
+ "computer_mouse": "🖱",
+ "confetti_ball": "🎊",
+ "confounded_face": "😖",
+ "confused_face": "😕",
+ "construction": "🚧",
+ "construction_worker": "👷",
+ "construction_worker_dark_skin_tone": "👷🏿",
+ "construction_worker_light_skin_tone": "👷🏻",
+ "construction_worker_medium-dark_skin_tone": "👷🏾",
+ "construction_worker_medium-light_skin_tone": "👷🏼",
+ "construction_worker_medium_skin_tone": "👷🏽",
+ "control_knobs": "🎛",
+ "convenience_store": "🏪",
+ "cooked_rice": "🍚",
+ "cookie": "🍪",
+ "cooking": "🍳",
+ "copyright": "©",
+ "couch_and_lamp": "🛋",
+ "counterclockwise_arrows_button": "🔄",
+ "couple_with_heart": "💑",
+ "couple_with_heart_man_man": "👨\u200d❤️\u200d👨",
+ "couple_with_heart_woman_man": "👩\u200d❤️\u200d👨",
+ "couple_with_heart_woman_woman": "👩\u200d❤️\u200d👩",
+ "cow": "🐮",
+ "cow_face": "🐮",
+ "cowboy_hat_face": "🤠",
+ "crab": "🦀",
+ "crayon": "🖍",
+ "credit_card": "💳",
+ "crescent_moon": "🌙",
+ "cricket": "🦗",
+ "cricket_game": "🏏",
+ "crocodile": "🐊",
+ "croissant": "🥐",
+ "cross_mark": "❌",
+ "cross_mark_button": "❎",
+ "crossed_fingers": "🤞",
+ "crossed_fingers_dark_skin_tone": "🤞🏿",
+ "crossed_fingers_light_skin_tone": "🤞🏻",
+ "crossed_fingers_medium-dark_skin_tone": "🤞🏾",
+ "crossed_fingers_medium-light_skin_tone": "🤞🏼",
+ "crossed_fingers_medium_skin_tone": "🤞🏽",
+ "crossed_flags": "🎌",
+ "crossed_swords": "⚔",
+ "crown": "👑",
+ "crying_cat_face": "😿",
+ "crying_face": "😢",
+ "crystal_ball": "🔮",
+ "cucumber": "🥒",
+ "cupcake": "🧁",
+ "cup_with_straw": "🥤",
+ "curling_stone": "🥌",
+ "curly_hair": "🦱",
+ "curly-haired_man": "👨\u200d🦱",
+ "curly-haired_woman": "👩\u200d🦱",
+ "curly_loop": "➰",
+ "currency_exchange": "💱",
+ "curry_rice": "🍛",
+ "custard": "🍮",
+ "customs": "🛃",
+ "cut_of_meat": "🥩",
+ "cyclone": "🌀",
+ "dagger": "🗡",
+ "dango": "🍡",
+ "dashing_away": "💨",
+ "deaf_person": "🧏",
+ "deciduous_tree": "🌳",
+ "deer": "🦌",
+ "delivery_truck": "🚚",
+ "department_store": "🏬",
+ "derelict_house": "🏚",
+ "desert": "🏜",
+ "desert_island": "🏝",
+ "desktop_computer": "🖥",
+ "detective": "🕵",
+ "detective_dark_skin_tone": "🕵🏿",
+ "detective_light_skin_tone": "🕵🏻",
+ "detective_medium-dark_skin_tone": "🕵🏾",
+ "detective_medium-light_skin_tone": "🕵🏼",
+ "detective_medium_skin_tone": "🕵🏽",
+ "diamond_suit": "♦",
+ "diamond_with_a_dot": "💠",
+ "dim_button": "🔅",
+ "direct_hit": "🎯",
+ "disappointed_face": "😞",
+ "diving_mask": "🤿",
+ "diya_lamp": "🪔",
+ "dizzy": "💫",
+ "dizzy_face": "😵",
+ "dna": "🧬",
+ "dog": "🐶",
+ "dog_face": "🐶",
+ "dollar_banknote": "💵",
+ "dolphin": "🐬",
+ "door": "🚪",
+ "dotted_six-pointed_star": "🔯",
+ "double_curly_loop": "➿",
+ "double_exclamation_mark": "‼",
+ "doughnut": "🍩",
+ "dove": "🕊",
+ "down-left_arrow": "↙",
+ "down-right_arrow": "↘",
+ "down_arrow": "⬇",
+ "downcast_face_with_sweat": "😓",
+ "downwards_button": "🔽",
+ "dragon": "🐉",
+ "dragon_face": "🐲",
+ "dress": "👗",
+ "drooling_face": "🤤",
+ "drop_of_blood": "🩸",
+ "droplet": "💧",
+ "drum": "🥁",
+ "duck": "🦆",
+ "dumpling": "🥟",
+ "dvd": "📀",
+ "e-mail": "📧",
+ "eagle": "🦅",
+ "ear": "👂",
+ "ear_dark_skin_tone": "👂🏿",
+ "ear_light_skin_tone": "👂🏻",
+ "ear_medium-dark_skin_tone": "👂🏾",
+ "ear_medium-light_skin_tone": "👂🏼",
+ "ear_medium_skin_tone": "👂🏽",
+ "ear_of_corn": "🌽",
+ "ear_with_hearing_aid": "🦻",
+ "egg": "🍳",
+ "eggplant": "🍆",
+ "eight-pointed_star": "✴",
+ "eight-spoked_asterisk": "✳",
+ "eight-thirty": "🕣",
+ "eight_o’clock": "🕗",
+ "eject_button": "⏏",
+ "electric_plug": "🔌",
+ "elephant": "🐘",
+ "eleven-thirty": "🕦",
+ "eleven_o’clock": "🕚",
+ "elf": "🧝",
+ "elf_dark_skin_tone": "🧝🏿",
+ "elf_light_skin_tone": "🧝🏻",
+ "elf_medium-dark_skin_tone": "🧝🏾",
+ "elf_medium-light_skin_tone": "🧝🏼",
+ "elf_medium_skin_tone": "🧝🏽",
+ "envelope": "✉",
+ "envelope_with_arrow": "📩",
+ "euro_banknote": "💶",
+ "evergreen_tree": "🌲",
+ "ewe": "🐑",
+ "exclamation_mark": "❗",
+ "exclamation_question_mark": "⁉",
+ "exploding_head": "🤯",
+ "expressionless_face": "😑",
+ "eye": "👁",
+ "eye_in_speech_bubble": "👁️\u200d🗨️",
+ "eyes": "👀",
+ "face_blowing_a_kiss": "😘",
+ "face_savoring_food": "😋",
+ "face_screaming_in_fear": "😱",
+ "face_vomiting": "🤮",
+ "face_with_hand_over_mouth": "🤭",
+ "face_with_head-bandage": "🤕",
+ "face_with_medical_mask": "😷",
+ "face_with_monocle": "🧐",
+ "face_with_open_mouth": "😮",
+ "face_with_raised_eyebrow": "🤨",
+ "face_with_rolling_eyes": "🙄",
+ "face_with_steam_from_nose": "😤",
+ "face_with_symbols_on_mouth": "🤬",
+ "face_with_tears_of_joy": "😂",
+ "face_with_thermometer": "🤒",
+ "face_with_tongue": "😛",
+ "face_without_mouth": "😶",
+ "factory": "🏭",
+ "fairy": "🧚",
+ "fairy_dark_skin_tone": "🧚🏿",
+ "fairy_light_skin_tone": "🧚🏻",
+ "fairy_medium-dark_skin_tone": "🧚🏾",
+ "fairy_medium-light_skin_tone": "🧚🏼",
+ "fairy_medium_skin_tone": "🧚🏽",
+ "falafel": "🧆",
+ "fallen_leaf": "🍂",
+ "family": "👪",
+ "family_man_boy": "👨\u200d👦",
+ "family_man_boy_boy": "👨\u200d👦\u200d👦",
+ "family_man_girl": "👨\u200d👧",
+ "family_man_girl_boy": "👨\u200d👧\u200d👦",
+ "family_man_girl_girl": "👨\u200d👧\u200d👧",
+ "family_man_man_boy": "👨\u200d👨\u200d👦",
+ "family_man_man_boy_boy": "👨\u200d👨\u200d👦\u200d👦",
+ "family_man_man_girl": "👨\u200d👨\u200d👧",
+ "family_man_man_girl_boy": "👨\u200d👨\u200d👧\u200d👦",
+ "family_man_man_girl_girl": "👨\u200d👨\u200d👧\u200d👧",
+ "family_man_woman_boy": "👨\u200d👩\u200d👦",
+ "family_man_woman_boy_boy": "👨\u200d👩\u200d👦\u200d👦",
+ "family_man_woman_girl": "👨\u200d👩\u200d👧",
+ "family_man_woman_girl_boy": "👨\u200d👩\u200d👧\u200d👦",
+ "family_man_woman_girl_girl": "👨\u200d👩\u200d👧\u200d👧",
+ "family_woman_boy": "👩\u200d👦",
+ "family_woman_boy_boy": "👩\u200d👦\u200d👦",
+ "family_woman_girl": "👩\u200d👧",
+ "family_woman_girl_boy": "👩\u200d👧\u200d👦",
+ "family_woman_girl_girl": "👩\u200d👧\u200d👧",
+ "family_woman_woman_boy": "👩\u200d👩\u200d👦",
+ "family_woman_woman_boy_boy": "👩\u200d👩\u200d👦\u200d👦",
+ "family_woman_woman_girl": "👩\u200d👩\u200d👧",
+ "family_woman_woman_girl_boy": "👩\u200d👩\u200d👧\u200d👦",
+ "family_woman_woman_girl_girl": "👩\u200d👩\u200d👧\u200d👧",
+ "fast-forward_button": "⏩",
+ "fast_down_button": "⏬",
+ "fast_reverse_button": "⏪",
+ "fast_up_button": "⏫",
+ "fax_machine": "📠",
+ "fearful_face": "😨",
+ "female_sign": "♀",
+ "ferris_wheel": "🎡",
+ "ferry": "⛴",
+ "field_hockey": "🏑",
+ "file_cabinet": "🗄",
+ "file_folder": "📁",
+ "film_frames": "🎞",
+ "film_projector": "📽",
+ "fire": "🔥",
+ "fire_extinguisher": "🧯",
+ "firecracker": "🧨",
+ "fire_engine": "🚒",
+ "fireworks": "🎆",
+ "first_quarter_moon": "🌓",
+ "first_quarter_moon_face": "🌛",
+ "fish": "🐟",
+ "fish_cake_with_swirl": "🍥",
+ "fishing_pole": "🎣",
+ "five-thirty": "🕠",
+ "five_o’clock": "🕔",
+ "flag_in_hole": "⛳",
+ "flamingo": "🦩",
+ "flashlight": "🔦",
+ "flat_shoe": "🥿",
+ "fleur-de-lis": "⚜",
+ "flexed_biceps": "💪",
+ "flexed_biceps_dark_skin_tone": "💪🏿",
+ "flexed_biceps_light_skin_tone": "💪🏻",
+ "flexed_biceps_medium-dark_skin_tone": "💪🏾",
+ "flexed_biceps_medium-light_skin_tone": "💪🏼",
+ "flexed_biceps_medium_skin_tone": "💪🏽",
+ "floppy_disk": "💾",
+ "flower_playing_cards": "🎴",
+ "flushed_face": "😳",
+ "flying_disc": "🥏",
+ "flying_saucer": "🛸",
+ "fog": "🌫",
+ "foggy": "🌁",
+ "folded_hands": "🙏",
+ "folded_hands_dark_skin_tone": "🙏🏿",
+ "folded_hands_light_skin_tone": "🙏🏻",
+ "folded_hands_medium-dark_skin_tone": "🙏🏾",
+ "folded_hands_medium-light_skin_tone": "🙏🏼",
+ "folded_hands_medium_skin_tone": "🙏🏽",
+ "foot": "🦶",
+ "footprints": "👣",
+ "fork_and_knife": "🍴",
+ "fork_and_knife_with_plate": "🍽",
+ "fortune_cookie": "🥠",
+ "fountain": "⛲",
+ "fountain_pen": "🖋",
+ "four-thirty": "🕟",
+ "four_leaf_clover": "🍀",
+ "four_o’clock": "🕓",
+ "fox_face": "🦊",
+ "framed_picture": "🖼",
+ "french_fries": "🍟",
+ "fried_shrimp": "🍤",
+ "frog_face": "🐸",
+ "front-facing_baby_chick": "🐥",
+ "frowning_face": "☹",
+ "frowning_face_with_open_mouth": "😦",
+ "fuel_pump": "⛽",
+ "full_moon": "🌕",
+ "full_moon_face": "🌝",
+ "funeral_urn": "⚱",
+ "game_die": "🎲",
+ "garlic": "🧄",
+ "gear": "⚙",
+ "gem_stone": "💎",
+ "genie": "🧞",
+ "ghost": "👻",
+ "giraffe": "🦒",
+ "girl": "👧",
+ "girl_dark_skin_tone": "👧🏿",
+ "girl_light_skin_tone": "👧🏻",
+ "girl_medium-dark_skin_tone": "👧🏾",
+ "girl_medium-light_skin_tone": "👧🏼",
+ "girl_medium_skin_tone": "👧🏽",
+ "glass_of_milk": "🥛",
+ "glasses": "👓",
+ "globe_showing_americas": "🌎",
+ "globe_showing_asia-australia": "🌏",
+ "globe_showing_europe-africa": "🌍",
+ "globe_with_meridians": "🌐",
+ "gloves": "🧤",
+ "glowing_star": "🌟",
+ "goal_net": "🥅",
+ "goat": "🐐",
+ "goblin": "👺",
+ "goggles": "🥽",
+ "gorilla": "🦍",
+ "graduation_cap": "🎓",
+ "grapes": "🍇",
+ "green_apple": "🍏",
+ "green_book": "📗",
+ "green_circle": "🟢",
+ "green_heart": "💚",
+ "green_salad": "🥗",
+ "green_square": "🟩",
+ "grimacing_face": "😬",
+ "grinning_cat_face": "😺",
+ "grinning_cat_face_with_smiling_eyes": "😸",
+ "grinning_face": "😀",
+ "grinning_face_with_big_eyes": "😃",
+ "grinning_face_with_smiling_eyes": "😄",
+ "grinning_face_with_sweat": "😅",
+ "grinning_squinting_face": "😆",
+ "growing_heart": "💗",
+ "guard": "💂",
+ "guard_dark_skin_tone": "💂🏿",
+ "guard_light_skin_tone": "💂🏻",
+ "guard_medium-dark_skin_tone": "💂🏾",
+ "guard_medium-light_skin_tone": "💂🏼",
+ "guard_medium_skin_tone": "💂🏽",
+ "guide_dog": "🦮",
+ "guitar": "🎸",
+ "hamburger": "🍔",
+ "hammer": "🔨",
+ "hammer_and_pick": "⚒",
+ "hammer_and_wrench": "🛠",
+ "hamster_face": "🐹",
+ "hand_with_fingers_splayed": "🖐",
+ "hand_with_fingers_splayed_dark_skin_tone": "🖐🏿",
+ "hand_with_fingers_splayed_light_skin_tone": "🖐🏻",
+ "hand_with_fingers_splayed_medium-dark_skin_tone": "🖐🏾",
+ "hand_with_fingers_splayed_medium-light_skin_tone": "🖐🏼",
+ "hand_with_fingers_splayed_medium_skin_tone": "🖐🏽",
+ "handbag": "👜",
+ "handshake": "🤝",
+ "hatching_chick": "🐣",
+ "headphone": "🎧",
+ "hear-no-evil_monkey": "🙉",
+ "heart_decoration": "💟",
+ "heart_suit": "♥",
+ "heart_with_arrow": "💘",
+ "heart_with_ribbon": "💝",
+ "heavy_check_mark": "✔",
+ "heavy_division_sign": "➗",
+ "heavy_dollar_sign": "💲",
+ "heavy_heart_exclamation": "❣",
+ "heavy_large_circle": "⭕",
+ "heavy_minus_sign": "➖",
+ "heavy_multiplication_x": "✖",
+ "heavy_plus_sign": "➕",
+ "hedgehog": "🦔",
+ "helicopter": "🚁",
+ "herb": "🌿",
+ "hibiscus": "🌺",
+ "high-heeled_shoe": "👠",
+ "high-speed_train": "🚄",
+ "high_voltage": "⚡",
+ "hiking_boot": "🥾",
+ "hindu_temple": "🛕",
+ "hippopotamus": "🦛",
+ "hole": "🕳",
+ "honey_pot": "🍯",
+ "honeybee": "🐝",
+ "horizontal_traffic_light": "🚥",
+ "horse": "🐴",
+ "horse_face": "🐴",
+ "horse_racing": "🏇",
+ "horse_racing_dark_skin_tone": "🏇🏿",
+ "horse_racing_light_skin_tone": "🏇🏻",
+ "horse_racing_medium-dark_skin_tone": "🏇🏾",
+ "horse_racing_medium-light_skin_tone": "🏇🏼",
+ "horse_racing_medium_skin_tone": "🏇🏽",
+ "hospital": "🏥",
+ "hot_beverage": "☕",
+ "hot_dog": "🌭",
+ "hot_face": "🥵",
+ "hot_pepper": "🌶",
+ "hot_springs": "♨",
+ "hotel": "🏨",
+ "hourglass_done": "⌛",
+ "hourglass_not_done": "⏳",
+ "house": "🏠",
+ "house_with_garden": "🏡",
+ "houses": "🏘",
+ "hugging_face": "🤗",
+ "hundred_points": "💯",
+ "hushed_face": "😯",
+ "ice": "🧊",
+ "ice_cream": "🍨",
+ "ice_hockey": "🏒",
+ "ice_skate": "⛸",
+ "inbox_tray": "📥",
+ "incoming_envelope": "📨",
+ "index_pointing_up": "☝",
+ "index_pointing_up_dark_skin_tone": "☝🏿",
+ "index_pointing_up_light_skin_tone": "☝🏻",
+ "index_pointing_up_medium-dark_skin_tone": "☝🏾",
+ "index_pointing_up_medium-light_skin_tone": "☝🏼",
+ "index_pointing_up_medium_skin_tone": "☝🏽",
+ "infinity": "♾",
+ "information": "ℹ",
+ "input_latin_letters": "🔤",
+ "input_latin_lowercase": "🔡",
+ "input_latin_uppercase": "🔠",
+ "input_numbers": "🔢",
+ "input_symbols": "🔣",
+ "jack-o-lantern": "🎃",
+ "jeans": "👖",
+ "jigsaw": "🧩",
+ "joker": "🃏",
+ "joystick": "🕹",
+ "kaaba": "🕋",
+ "kangaroo": "🦘",
+ "key": "🔑",
+ "keyboard": "⌨",
+ "keycap_#": "#️⃣",
+ "keycap_*": "*️⃣",
+ "keycap_0": "0️⃣",
+ "keycap_1": "1️⃣",
+ "keycap_10": "🔟",
+ "keycap_2": "2️⃣",
+ "keycap_3": "3️⃣",
+ "keycap_4": "4️⃣",
+ "keycap_5": "5️⃣",
+ "keycap_6": "6️⃣",
+ "keycap_7": "7️⃣",
+ "keycap_8": "8️⃣",
+ "keycap_9": "9️⃣",
+ "kick_scooter": "🛴",
+ "kimono": "👘",
+ "kiss": "💋",
+ "kiss_man_man": "👨\u200d❤️\u200d💋\u200d👨",
+ "kiss_mark": "💋",
+ "kiss_woman_man": "👩\u200d❤️\u200d💋\u200d👨",
+ "kiss_woman_woman": "👩\u200d❤️\u200d💋\u200d👩",
+ "kissing_cat_face": "😽",
+ "kissing_face": "😗",
+ "kissing_face_with_closed_eyes": "😚",
+ "kissing_face_with_smiling_eyes": "😙",
+ "kitchen_knife": "🔪",
+ "kite": "🪁",
+ "kiwi_fruit": "🥝",
+ "koala": "🐨",
+ "lab_coat": "🥼",
+ "label": "🏷",
+ "lacrosse": "🥍",
+ "lady_beetle": "🐞",
+ "laptop_computer": "💻",
+ "large_blue_diamond": "🔷",
+ "large_orange_diamond": "🔶",
+ "last_quarter_moon": "🌗",
+ "last_quarter_moon_face": "🌜",
+ "last_track_button": "⏮",
+ "latin_cross": "✝",
+ "leaf_fluttering_in_wind": "🍃",
+ "leafy_green": "🥬",
+ "ledger": "📒",
+ "left-facing_fist": "🤛",
+ "left-facing_fist_dark_skin_tone": "🤛🏿",
+ "left-facing_fist_light_skin_tone": "🤛🏻",
+ "left-facing_fist_medium-dark_skin_tone": "🤛🏾",
+ "left-facing_fist_medium-light_skin_tone": "🤛🏼",
+ "left-facing_fist_medium_skin_tone": "🤛🏽",
+ "left-right_arrow": "↔",
+ "left_arrow": "⬅",
+ "left_arrow_curving_right": "↪",
+ "left_luggage": "🛅",
+ "left_speech_bubble": "🗨",
+ "leg": "🦵",
+ "lemon": "🍋",
+ "leopard": "🐆",
+ "level_slider": "🎚",
+ "light_bulb": "💡",
+ "light_rail": "🚈",
+ "link": "🔗",
+ "linked_paperclips": "🖇",
+ "lion_face": "🦁",
+ "lipstick": "💄",
+ "litter_in_bin_sign": "🚮",
+ "lizard": "🦎",
+ "llama": "🦙",
+ "lobster": "🦞",
+ "locked": "🔒",
+ "locked_with_key": "🔐",
+ "locked_with_pen": "🔏",
+ "locomotive": "🚂",
+ "lollipop": "🍭",
+ "lotion_bottle": "🧴",
+ "loudly_crying_face": "😭",
+ "loudspeaker": "📢",
+ "love-you_gesture": "🤟",
+ "love-you_gesture_dark_skin_tone": "🤟🏿",
+ "love-you_gesture_light_skin_tone": "🤟🏻",
+ "love-you_gesture_medium-dark_skin_tone": "🤟🏾",
+ "love-you_gesture_medium-light_skin_tone": "🤟🏼",
+ "love-you_gesture_medium_skin_tone": "🤟🏽",
+ "love_hotel": "🏩",
+ "love_letter": "💌",
+ "luggage": "🧳",
+ "lying_face": "🤥",
+ "mage": "🧙",
+ "mage_dark_skin_tone": "🧙🏿",
+ "mage_light_skin_tone": "🧙🏻",
+ "mage_medium-dark_skin_tone": "🧙🏾",
+ "mage_medium-light_skin_tone": "🧙🏼",
+ "mage_medium_skin_tone": "🧙🏽",
+ "magnet": "🧲",
+ "magnifying_glass_tilted_left": "🔍",
+ "magnifying_glass_tilted_right": "🔎",
+ "mahjong_red_dragon": "🀄",
+ "male_sign": "♂",
+ "man": "👨",
+ "man_and_woman_holding_hands": "👫",
+ "man_artist": "👨\u200d🎨",
+ "man_artist_dark_skin_tone": "👨🏿\u200d🎨",
+ "man_artist_light_skin_tone": "👨🏻\u200d🎨",
+ "man_artist_medium-dark_skin_tone": "👨🏾\u200d🎨",
+ "man_artist_medium-light_skin_tone": "👨🏼\u200d🎨",
+ "man_artist_medium_skin_tone": "👨🏽\u200d🎨",
+ "man_astronaut": "👨\u200d🚀",
+ "man_astronaut_dark_skin_tone": "👨🏿\u200d🚀",
+ "man_astronaut_light_skin_tone": "👨🏻\u200d🚀",
+ "man_astronaut_medium-dark_skin_tone": "👨🏾\u200d🚀",
+ "man_astronaut_medium-light_skin_tone": "👨🏼\u200d🚀",
+ "man_astronaut_medium_skin_tone": "👨🏽\u200d🚀",
+ "man_biking": "🚴\u200d♂️",
+ "man_biking_dark_skin_tone": "🚴🏿\u200d♂️",
+ "man_biking_light_skin_tone": "🚴🏻\u200d♂️",
+ "man_biking_medium-dark_skin_tone": "🚴🏾\u200d♂️",
+ "man_biking_medium-light_skin_tone": "🚴🏼\u200d♂️",
+ "man_biking_medium_skin_tone": "🚴🏽\u200d♂️",
+ "man_bouncing_ball": "⛹️\u200d♂️",
+ "man_bouncing_ball_dark_skin_tone": "⛹🏿\u200d♂️",
+ "man_bouncing_ball_light_skin_tone": "⛹🏻\u200d♂️",
+ "man_bouncing_ball_medium-dark_skin_tone": "⛹🏾\u200d♂️",
+ "man_bouncing_ball_medium-light_skin_tone": "⛹🏼\u200d♂️",
+ "man_bouncing_ball_medium_skin_tone": "⛹🏽\u200d♂️",
+ "man_bowing": "🙇\u200d♂️",
+ "man_bowing_dark_skin_tone": "🙇🏿\u200d♂️",
+ "man_bowing_light_skin_tone": "🙇🏻\u200d♂️",
+ "man_bowing_medium-dark_skin_tone": "🙇🏾\u200d♂️",
+ "man_bowing_medium-light_skin_tone": "🙇🏼\u200d♂️",
+ "man_bowing_medium_skin_tone": "🙇🏽\u200d♂️",
+ "man_cartwheeling": "🤸\u200d♂️",
+ "man_cartwheeling_dark_skin_tone": "🤸🏿\u200d♂️",
+ "man_cartwheeling_light_skin_tone": "🤸🏻\u200d♂️",
+ "man_cartwheeling_medium-dark_skin_tone": "🤸🏾\u200d♂️",
+ "man_cartwheeling_medium-light_skin_tone": "🤸🏼\u200d♂️",
+ "man_cartwheeling_medium_skin_tone": "🤸🏽\u200d♂️",
+ "man_climbing": "🧗\u200d♂️",
+ "man_climbing_dark_skin_tone": "🧗🏿\u200d♂️",
+ "man_climbing_light_skin_tone": "🧗🏻\u200d♂️",
+ "man_climbing_medium-dark_skin_tone": "🧗🏾\u200d♂️",
+ "man_climbing_medium-light_skin_tone": "🧗🏼\u200d♂️",
+ "man_climbing_medium_skin_tone": "🧗🏽\u200d♂️",
+ "man_construction_worker": "👷\u200d♂️",
+ "man_construction_worker_dark_skin_tone": "👷🏿\u200d♂️",
+ "man_construction_worker_light_skin_tone": "👷🏻\u200d♂️",
+ "man_construction_worker_medium-dark_skin_tone": "👷🏾\u200d♂️",
+ "man_construction_worker_medium-light_skin_tone": "👷🏼\u200d♂️",
+ "man_construction_worker_medium_skin_tone": "👷🏽\u200d♂️",
+ "man_cook": "👨\u200d🍳",
+ "man_cook_dark_skin_tone": "👨🏿\u200d🍳",
+ "man_cook_light_skin_tone": "👨🏻\u200d🍳",
+ "man_cook_medium-dark_skin_tone": "👨🏾\u200d🍳",
+ "man_cook_medium-light_skin_tone": "👨🏼\u200d🍳",
+ "man_cook_medium_skin_tone": "👨🏽\u200d🍳",
+ "man_dancing": "🕺",
+ "man_dancing_dark_skin_tone": "🕺🏿",
+ "man_dancing_light_skin_tone": "🕺🏻",
+ "man_dancing_medium-dark_skin_tone": "🕺🏾",
+ "man_dancing_medium-light_skin_tone": "🕺🏼",
+ "man_dancing_medium_skin_tone": "🕺🏽",
+ "man_dark_skin_tone": "👨🏿",
+ "man_detective": "🕵️\u200d♂️",
+ "man_detective_dark_skin_tone": "🕵🏿\u200d♂️",
+ "man_detective_light_skin_tone": "🕵🏻\u200d♂️",
+ "man_detective_medium-dark_skin_tone": "🕵🏾\u200d♂️",
+ "man_detective_medium-light_skin_tone": "🕵🏼\u200d♂️",
+ "man_detective_medium_skin_tone": "🕵🏽\u200d♂️",
+ "man_elf": "🧝\u200d♂️",
+ "man_elf_dark_skin_tone": "🧝🏿\u200d♂️",
+ "man_elf_light_skin_tone": "🧝🏻\u200d♂️",
+ "man_elf_medium-dark_skin_tone": "🧝🏾\u200d♂️",
+ "man_elf_medium-light_skin_tone": "🧝🏼\u200d♂️",
+ "man_elf_medium_skin_tone": "🧝🏽\u200d♂️",
+ "man_facepalming": "🤦\u200d♂️",
+ "man_facepalming_dark_skin_tone": "🤦🏿\u200d♂️",
+ "man_facepalming_light_skin_tone": "🤦🏻\u200d♂️",
+ "man_facepalming_medium-dark_skin_tone": "🤦🏾\u200d♂️",
+ "man_facepalming_medium-light_skin_tone": "🤦🏼\u200d♂️",
+ "man_facepalming_medium_skin_tone": "🤦🏽\u200d♂️",
+ "man_factory_worker": "👨\u200d🏭",
+ "man_factory_worker_dark_skin_tone": "👨🏿\u200d🏭",
+ "man_factory_worker_light_skin_tone": "👨🏻\u200d🏭",
+ "man_factory_worker_medium-dark_skin_tone": "👨🏾\u200d🏭",
+ "man_factory_worker_medium-light_skin_tone": "👨🏼\u200d🏭",
+ "man_factory_worker_medium_skin_tone": "👨🏽\u200d🏭",
+ "man_fairy": "🧚\u200d♂️",
+ "man_fairy_dark_skin_tone": "🧚🏿\u200d♂️",
+ "man_fairy_light_skin_tone": "🧚🏻\u200d♂️",
+ "man_fairy_medium-dark_skin_tone": "🧚🏾\u200d♂️",
+ "man_fairy_medium-light_skin_tone": "🧚🏼\u200d♂️",
+ "man_fairy_medium_skin_tone": "🧚🏽\u200d♂️",
+ "man_farmer": "👨\u200d🌾",
+ "man_farmer_dark_skin_tone": "👨🏿\u200d🌾",
+ "man_farmer_light_skin_tone": "👨🏻\u200d🌾",
+ "man_farmer_medium-dark_skin_tone": "👨🏾\u200d🌾",
+ "man_farmer_medium-light_skin_tone": "👨🏼\u200d🌾",
+ "man_farmer_medium_skin_tone": "👨🏽\u200d🌾",
+ "man_firefighter": "👨\u200d🚒",
+ "man_firefighter_dark_skin_tone": "👨🏿\u200d🚒",
+ "man_firefighter_light_skin_tone": "👨🏻\u200d🚒",
+ "man_firefighter_medium-dark_skin_tone": "👨🏾\u200d🚒",
+ "man_firefighter_medium-light_skin_tone": "👨🏼\u200d🚒",
+ "man_firefighter_medium_skin_tone": "👨🏽\u200d🚒",
+ "man_frowning": "🙍\u200d♂️",
+ "man_frowning_dark_skin_tone": "🙍🏿\u200d♂️",
+ "man_frowning_light_skin_tone": "🙍🏻\u200d♂️",
+ "man_frowning_medium-dark_skin_tone": "🙍🏾\u200d♂️",
+ "man_frowning_medium-light_skin_tone": "🙍🏼\u200d♂️",
+ "man_frowning_medium_skin_tone": "🙍🏽\u200d♂️",
+ "man_genie": "🧞\u200d♂️",
+ "man_gesturing_no": "🙅\u200d♂️",
+ "man_gesturing_no_dark_skin_tone": "🙅🏿\u200d♂️",
+ "man_gesturing_no_light_skin_tone": "🙅🏻\u200d♂️",
+ "man_gesturing_no_medium-dark_skin_tone": "🙅🏾\u200d♂️",
+ "man_gesturing_no_medium-light_skin_tone": "🙅🏼\u200d♂️",
+ "man_gesturing_no_medium_skin_tone": "🙅🏽\u200d♂️",
+ "man_gesturing_ok": "🙆\u200d♂️",
+ "man_gesturing_ok_dark_skin_tone": "🙆🏿\u200d♂️",
+ "man_gesturing_ok_light_skin_tone": "🙆🏻\u200d♂️",
+ "man_gesturing_ok_medium-dark_skin_tone": "🙆🏾\u200d♂️",
+ "man_gesturing_ok_medium-light_skin_tone": "🙆🏼\u200d♂️",
+ "man_gesturing_ok_medium_skin_tone": "🙆🏽\u200d♂️",
+ "man_getting_haircut": "💇\u200d♂️",
+ "man_getting_haircut_dark_skin_tone": "💇🏿\u200d♂️",
+ "man_getting_haircut_light_skin_tone": "💇🏻\u200d♂️",
+ "man_getting_haircut_medium-dark_skin_tone": "💇🏾\u200d♂️",
+ "man_getting_haircut_medium-light_skin_tone": "💇🏼\u200d♂️",
+ "man_getting_haircut_medium_skin_tone": "💇🏽\u200d♂️",
+ "man_getting_massage": "💆\u200d♂️",
+ "man_getting_massage_dark_skin_tone": "💆🏿\u200d♂️",
+ "man_getting_massage_light_skin_tone": "💆🏻\u200d♂️",
+ "man_getting_massage_medium-dark_skin_tone": "💆🏾\u200d♂️",
+ "man_getting_massage_medium-light_skin_tone": "💆🏼\u200d♂️",
+ "man_getting_massage_medium_skin_tone": "💆🏽\u200d♂️",
+ "man_golfing": "🏌️\u200d♂️",
+ "man_golfing_dark_skin_tone": "🏌🏿\u200d♂️",
+ "man_golfing_light_skin_tone": "🏌🏻\u200d♂️",
+ "man_golfing_medium-dark_skin_tone": "🏌🏾\u200d♂️",
+ "man_golfing_medium-light_skin_tone": "🏌🏼\u200d♂️",
+ "man_golfing_medium_skin_tone": "🏌🏽\u200d♂️",
+ "man_guard": "💂\u200d♂️",
+ "man_guard_dark_skin_tone": "💂🏿\u200d♂️",
+ "man_guard_light_skin_tone": "💂🏻\u200d♂️",
+ "man_guard_medium-dark_skin_tone": "💂🏾\u200d♂️",
+ "man_guard_medium-light_skin_tone": "💂🏼\u200d♂️",
+ "man_guard_medium_skin_tone": "💂🏽\u200d♂️",
+ "man_health_worker": "👨\u200d⚕️",
+ "man_health_worker_dark_skin_tone": "👨🏿\u200d⚕️",
+ "man_health_worker_light_skin_tone": "👨🏻\u200d⚕️",
+ "man_health_worker_medium-dark_skin_tone": "👨🏾\u200d⚕️",
+ "man_health_worker_medium-light_skin_tone": "👨🏼\u200d⚕️",
+ "man_health_worker_medium_skin_tone": "👨🏽\u200d⚕️",
+ "man_in_lotus_position": "🧘\u200d♂️",
+ "man_in_lotus_position_dark_skin_tone": "🧘🏿\u200d♂️",
+ "man_in_lotus_position_light_skin_tone": "🧘🏻\u200d♂️",
+ "man_in_lotus_position_medium-dark_skin_tone": "🧘🏾\u200d♂️",
+ "man_in_lotus_position_medium-light_skin_tone": "🧘🏼\u200d♂️",
+ "man_in_lotus_position_medium_skin_tone": "🧘🏽\u200d♂️",
+ "man_in_manual_wheelchair": "👨\u200d🦽",
+ "man_in_motorized_wheelchair": "👨\u200d🦼",
+ "man_in_steamy_room": "🧖\u200d♂️",
+ "man_in_steamy_room_dark_skin_tone": "🧖🏿\u200d♂️",
+ "man_in_steamy_room_light_skin_tone": "🧖🏻\u200d♂️",
+ "man_in_steamy_room_medium-dark_skin_tone": "🧖🏾\u200d♂️",
+ "man_in_steamy_room_medium-light_skin_tone": "🧖🏼\u200d♂️",
+ "man_in_steamy_room_medium_skin_tone": "🧖🏽\u200d♂️",
+ "man_in_suit_levitating": "🕴",
+ "man_in_suit_levitating_dark_skin_tone": "🕴🏿",
+ "man_in_suit_levitating_light_skin_tone": "🕴🏻",
+ "man_in_suit_levitating_medium-dark_skin_tone": "🕴🏾",
+ "man_in_suit_levitating_medium-light_skin_tone": "🕴🏼",
+ "man_in_suit_levitating_medium_skin_tone": "🕴🏽",
+ "man_in_tuxedo": "🤵",
+ "man_in_tuxedo_dark_skin_tone": "🤵🏿",
+ "man_in_tuxedo_light_skin_tone": "🤵🏻",
+ "man_in_tuxedo_medium-dark_skin_tone": "🤵🏾",
+ "man_in_tuxedo_medium-light_skin_tone": "🤵🏼",
+ "man_in_tuxedo_medium_skin_tone": "🤵🏽",
+ "man_judge": "👨\u200d⚖️",
+ "man_judge_dark_skin_tone": "👨🏿\u200d⚖️",
+ "man_judge_light_skin_tone": "👨🏻\u200d⚖️",
+ "man_judge_medium-dark_skin_tone": "👨🏾\u200d⚖️",
+ "man_judge_medium-light_skin_tone": "👨🏼\u200d⚖️",
+ "man_judge_medium_skin_tone": "👨🏽\u200d⚖️",
+ "man_juggling": "🤹\u200d♂️",
+ "man_juggling_dark_skin_tone": "🤹🏿\u200d♂️",
+ "man_juggling_light_skin_tone": "🤹🏻\u200d♂️",
+ "man_juggling_medium-dark_skin_tone": "🤹🏾\u200d♂️",
+ "man_juggling_medium-light_skin_tone": "🤹🏼\u200d♂️",
+ "man_juggling_medium_skin_tone": "🤹🏽\u200d♂️",
+ "man_lifting_weights": "🏋️\u200d♂️",
+ "man_lifting_weights_dark_skin_tone": "🏋🏿\u200d♂️",
+ "man_lifting_weights_light_skin_tone": "🏋🏻\u200d♂️",
+ "man_lifting_weights_medium-dark_skin_tone": "🏋🏾\u200d♂️",
+ "man_lifting_weights_medium-light_skin_tone": "🏋🏼\u200d♂️",
+ "man_lifting_weights_medium_skin_tone": "🏋🏽\u200d♂️",
+ "man_light_skin_tone": "👨🏻",
+ "man_mage": "🧙\u200d♂️",
+ "man_mage_dark_skin_tone": "🧙🏿\u200d♂️",
+ "man_mage_light_skin_tone": "🧙🏻\u200d♂️",
+ "man_mage_medium-dark_skin_tone": "🧙🏾\u200d♂️",
+ "man_mage_medium-light_skin_tone": "🧙🏼\u200d♂️",
+ "man_mage_medium_skin_tone": "🧙🏽\u200d♂️",
+ "man_mechanic": "👨\u200d🔧",
+ "man_mechanic_dark_skin_tone": "👨🏿\u200d🔧",
+ "man_mechanic_light_skin_tone": "👨🏻\u200d🔧",
+ "man_mechanic_medium-dark_skin_tone": "👨🏾\u200d🔧",
+ "man_mechanic_medium-light_skin_tone": "👨🏼\u200d🔧",
+ "man_mechanic_medium_skin_tone": "👨🏽\u200d🔧",
+ "man_medium-dark_skin_tone": "👨🏾",
+ "man_medium-light_skin_tone": "👨🏼",
+ "man_medium_skin_tone": "👨🏽",
+ "man_mountain_biking": "🚵\u200d♂️",
+ "man_mountain_biking_dark_skin_tone": "🚵🏿\u200d♂️",
+ "man_mountain_biking_light_skin_tone": "🚵🏻\u200d♂️",
+ "man_mountain_biking_medium-dark_skin_tone": "🚵🏾\u200d♂️",
+ "man_mountain_biking_medium-light_skin_tone": "🚵🏼\u200d♂️",
+ "man_mountain_biking_medium_skin_tone": "🚵🏽\u200d♂️",
+ "man_office_worker": "👨\u200d💼",
+ "man_office_worker_dark_skin_tone": "👨🏿\u200d💼",
+ "man_office_worker_light_skin_tone": "👨🏻\u200d💼",
+ "man_office_worker_medium-dark_skin_tone": "👨🏾\u200d💼",
+ "man_office_worker_medium-light_skin_tone": "👨🏼\u200d💼",
+ "man_office_worker_medium_skin_tone": "👨🏽\u200d💼",
+ "man_pilot": "👨\u200d✈️",
+ "man_pilot_dark_skin_tone": "👨🏿\u200d✈️",
+ "man_pilot_light_skin_tone": "👨🏻\u200d✈️",
+ "man_pilot_medium-dark_skin_tone": "👨🏾\u200d✈️",
+ "man_pilot_medium-light_skin_tone": "👨🏼\u200d✈️",
+ "man_pilot_medium_skin_tone": "👨🏽\u200d✈️",
+ "man_playing_handball": "🤾\u200d♂️",
+ "man_playing_handball_dark_skin_tone": "🤾🏿\u200d♂️",
+ "man_playing_handball_light_skin_tone": "🤾🏻\u200d♂️",
+ "man_playing_handball_medium-dark_skin_tone": "🤾🏾\u200d♂️",
+ "man_playing_handball_medium-light_skin_tone": "🤾🏼\u200d♂️",
+ "man_playing_handball_medium_skin_tone": "🤾🏽\u200d♂️",
+ "man_playing_water_polo": "🤽\u200d♂️",
+ "man_playing_water_polo_dark_skin_tone": "🤽🏿\u200d♂️",
+ "man_playing_water_polo_light_skin_tone": "🤽🏻\u200d♂️",
+ "man_playing_water_polo_medium-dark_skin_tone": "🤽🏾\u200d♂️",
+ "man_playing_water_polo_medium-light_skin_tone": "🤽🏼\u200d♂️",
+ "man_playing_water_polo_medium_skin_tone": "🤽🏽\u200d♂️",
+ "man_police_officer": "👮\u200d♂️",
+ "man_police_officer_dark_skin_tone": "👮🏿\u200d♂️",
+ "man_police_officer_light_skin_tone": "👮🏻\u200d♂️",
+ "man_police_officer_medium-dark_skin_tone": "👮🏾\u200d♂️",
+ "man_police_officer_medium-light_skin_tone": "👮🏼\u200d♂️",
+ "man_police_officer_medium_skin_tone": "👮🏽\u200d♂️",
+ "man_pouting": "🙎\u200d♂️",
+ "man_pouting_dark_skin_tone": "🙎🏿\u200d♂️",
+ "man_pouting_light_skin_tone": "🙎🏻\u200d♂️",
+ "man_pouting_medium-dark_skin_tone": "🙎🏾\u200d♂️",
+ "man_pouting_medium-light_skin_tone": "🙎🏼\u200d♂️",
+ "man_pouting_medium_skin_tone": "🙎🏽\u200d♂️",
+ "man_raising_hand": "🙋\u200d♂️",
+ "man_raising_hand_dark_skin_tone": "🙋🏿\u200d♂️",
+ "man_raising_hand_light_skin_tone": "🙋🏻\u200d♂️",
+ "man_raising_hand_medium-dark_skin_tone": "🙋🏾\u200d♂️",
+ "man_raising_hand_medium-light_skin_tone": "🙋🏼\u200d♂️",
+ "man_raising_hand_medium_skin_tone": "🙋🏽\u200d♂️",
+ "man_rowing_boat": "🚣\u200d♂️",
+ "man_rowing_boat_dark_skin_tone": "🚣🏿\u200d♂️",
+ "man_rowing_boat_light_skin_tone": "🚣🏻\u200d♂️",
+ "man_rowing_boat_medium-dark_skin_tone": "🚣🏾\u200d♂️",
+ "man_rowing_boat_medium-light_skin_tone": "🚣🏼\u200d♂️",
+ "man_rowing_boat_medium_skin_tone": "🚣🏽\u200d♂️",
+ "man_running": "🏃\u200d♂️",
+ "man_running_dark_skin_tone": "🏃🏿\u200d♂️",
+ "man_running_light_skin_tone": "🏃🏻\u200d♂️",
+ "man_running_medium-dark_skin_tone": "🏃🏾\u200d♂️",
+ "man_running_medium-light_skin_tone": "🏃🏼\u200d♂️",
+ "man_running_medium_skin_tone": "🏃🏽\u200d♂️",
+ "man_scientist": "👨\u200d🔬",
+ "man_scientist_dark_skin_tone": "👨🏿\u200d🔬",
+ "man_scientist_light_skin_tone": "👨🏻\u200d🔬",
+ "man_scientist_medium-dark_skin_tone": "👨🏾\u200d🔬",
+ "man_scientist_medium-light_skin_tone": "👨🏼\u200d🔬",
+ "man_scientist_medium_skin_tone": "👨🏽\u200d🔬",
+ "man_shrugging": "🤷\u200d♂️",
+ "man_shrugging_dark_skin_tone": "🤷🏿\u200d♂️",
+ "man_shrugging_light_skin_tone": "🤷🏻\u200d♂️",
+ "man_shrugging_medium-dark_skin_tone": "🤷🏾\u200d♂️",
+ "man_shrugging_medium-light_skin_tone": "🤷🏼\u200d♂️",
+ "man_shrugging_medium_skin_tone": "🤷🏽\u200d♂️",
+ "man_singer": "👨\u200d🎤",
+ "man_singer_dark_skin_tone": "👨🏿\u200d🎤",
+ "man_singer_light_skin_tone": "👨🏻\u200d🎤",
+ "man_singer_medium-dark_skin_tone": "👨🏾\u200d🎤",
+ "man_singer_medium-light_skin_tone": "👨🏼\u200d🎤",
+ "man_singer_medium_skin_tone": "👨🏽\u200d🎤",
+ "man_student": "👨\u200d🎓",
+ "man_student_dark_skin_tone": "👨🏿\u200d🎓",
+ "man_student_light_skin_tone": "👨🏻\u200d🎓",
+ "man_student_medium-dark_skin_tone": "👨🏾\u200d🎓",
+ "man_student_medium-light_skin_tone": "👨🏼\u200d🎓",
+ "man_student_medium_skin_tone": "👨🏽\u200d🎓",
+ "man_surfing": "🏄\u200d♂️",
+ "man_surfing_dark_skin_tone": "🏄🏿\u200d♂️",
+ "man_surfing_light_skin_tone": "🏄🏻\u200d♂️",
+ "man_surfing_medium-dark_skin_tone": "🏄🏾\u200d♂️",
+ "man_surfing_medium-light_skin_tone": "🏄🏼\u200d♂️",
+ "man_surfing_medium_skin_tone": "🏄🏽\u200d♂️",
+ "man_swimming": "🏊\u200d♂️",
+ "man_swimming_dark_skin_tone": "🏊🏿\u200d♂️",
+ "man_swimming_light_skin_tone": "🏊🏻\u200d♂️",
+ "man_swimming_medium-dark_skin_tone": "🏊🏾\u200d♂️",
+ "man_swimming_medium-light_skin_tone": "🏊🏼\u200d♂️",
+ "man_swimming_medium_skin_tone": "🏊🏽\u200d♂️",
+ "man_teacher": "👨\u200d🏫",
+ "man_teacher_dark_skin_tone": "👨🏿\u200d🏫",
+ "man_teacher_light_skin_tone": "👨🏻\u200d🏫",
+ "man_teacher_medium-dark_skin_tone": "👨🏾\u200d🏫",
+ "man_teacher_medium-light_skin_tone": "👨🏼\u200d🏫",
+ "man_teacher_medium_skin_tone": "👨🏽\u200d🏫",
+ "man_technologist": "👨\u200d💻",
+ "man_technologist_dark_skin_tone": "👨🏿\u200d💻",
+ "man_technologist_light_skin_tone": "👨🏻\u200d💻",
+ "man_technologist_medium-dark_skin_tone": "👨🏾\u200d💻",
+ "man_technologist_medium-light_skin_tone": "👨🏼\u200d💻",
+ "man_technologist_medium_skin_tone": "👨🏽\u200d💻",
+ "man_tipping_hand": "💁\u200d♂️",
+ "man_tipping_hand_dark_skin_tone": "💁🏿\u200d♂️",
+ "man_tipping_hand_light_skin_tone": "💁🏻\u200d♂️",
+ "man_tipping_hand_medium-dark_skin_tone": "💁🏾\u200d♂️",
+ "man_tipping_hand_medium-light_skin_tone": "💁🏼\u200d♂️",
+ "man_tipping_hand_medium_skin_tone": "💁🏽\u200d♂️",
+ "man_vampire": "🧛\u200d♂️",
+ "man_vampire_dark_skin_tone": "🧛🏿\u200d♂️",
+ "man_vampire_light_skin_tone": "🧛🏻\u200d♂️",
+ "man_vampire_medium-dark_skin_tone": "🧛🏾\u200d♂️",
+ "man_vampire_medium-light_skin_tone": "🧛🏼\u200d♂️",
+ "man_vampire_medium_skin_tone": "🧛🏽\u200d♂️",
+ "man_walking": "🚶\u200d♂️",
+ "man_walking_dark_skin_tone": "🚶🏿\u200d♂️",
+ "man_walking_light_skin_tone": "🚶🏻\u200d♂️",
+ "man_walking_medium-dark_skin_tone": "🚶🏾\u200d♂️",
+ "man_walking_medium-light_skin_tone": "🚶🏼\u200d♂️",
+ "man_walking_medium_skin_tone": "🚶🏽\u200d♂️",
+ "man_wearing_turban": "👳\u200d♂️",
+ "man_wearing_turban_dark_skin_tone": "👳🏿\u200d♂️",
+ "man_wearing_turban_light_skin_tone": "👳🏻\u200d♂️",
+ "man_wearing_turban_medium-dark_skin_tone": "👳🏾\u200d♂️",
+ "man_wearing_turban_medium-light_skin_tone": "👳🏼\u200d♂️",
+ "man_wearing_turban_medium_skin_tone": "👳🏽\u200d♂️",
+ "man_with_probing_cane": "👨\u200d🦯",
+ "man_with_chinese_cap": "👲",
+ "man_with_chinese_cap_dark_skin_tone": "👲🏿",
+ "man_with_chinese_cap_light_skin_tone": "👲🏻",
+ "man_with_chinese_cap_medium-dark_skin_tone": "👲🏾",
+ "man_with_chinese_cap_medium-light_skin_tone": "👲🏼",
+ "man_with_chinese_cap_medium_skin_tone": "👲🏽",
+ "man_zombie": "🧟\u200d♂️",
+ "mango": "🥭",
+ "mantelpiece_clock": "🕰",
+ "manual_wheelchair": "🦽",
+ "man’s_shoe": "👞",
+ "map_of_japan": "🗾",
+ "maple_leaf": "🍁",
+ "martial_arts_uniform": "🥋",
+ "mate": "🧉",
+ "meat_on_bone": "🍖",
+ "mechanical_arm": "🦾",
+ "mechanical_leg": "🦿",
+ "medical_symbol": "⚕",
+ "megaphone": "📣",
+ "melon": "🍈",
+ "memo": "📝",
+ "men_with_bunny_ears": "👯\u200d♂️",
+ "men_wrestling": "🤼\u200d♂️",
+ "menorah": "🕎",
+ "men’s_room": "🚹",
+ "mermaid": "🧜\u200d♀️",
+ "mermaid_dark_skin_tone": "🧜🏿\u200d♀️",
+ "mermaid_light_skin_tone": "🧜🏻\u200d♀️",
+ "mermaid_medium-dark_skin_tone": "🧜🏾\u200d♀️",
+ "mermaid_medium-light_skin_tone": "🧜🏼\u200d♀️",
+ "mermaid_medium_skin_tone": "🧜🏽\u200d♀️",
+ "merman": "🧜\u200d♂️",
+ "merman_dark_skin_tone": "🧜🏿\u200d♂️",
+ "merman_light_skin_tone": "🧜🏻\u200d♂️",
+ "merman_medium-dark_skin_tone": "🧜🏾\u200d♂️",
+ "merman_medium-light_skin_tone": "🧜🏼\u200d♂️",
+ "merman_medium_skin_tone": "🧜🏽\u200d♂️",
+ "merperson": "🧜",
+ "merperson_dark_skin_tone": "🧜🏿",
+ "merperson_light_skin_tone": "🧜🏻",
+ "merperson_medium-dark_skin_tone": "🧜🏾",
+ "merperson_medium-light_skin_tone": "🧜🏼",
+ "merperson_medium_skin_tone": "🧜🏽",
+ "metro": "🚇",
+ "microbe": "🦠",
+ "microphone": "🎤",
+ "microscope": "🔬",
+ "middle_finger": "🖕",
+ "middle_finger_dark_skin_tone": "🖕🏿",
+ "middle_finger_light_skin_tone": "🖕🏻",
+ "middle_finger_medium-dark_skin_tone": "🖕🏾",
+ "middle_finger_medium-light_skin_tone": "🖕🏼",
+ "middle_finger_medium_skin_tone": "🖕🏽",
+ "military_medal": "🎖",
+ "milky_way": "🌌",
+ "minibus": "🚐",
+ "moai": "🗿",
+ "mobile_phone": "📱",
+ "mobile_phone_off": "📴",
+ "mobile_phone_with_arrow": "📲",
+ "money-mouth_face": "🤑",
+ "money_bag": "💰",
+ "money_with_wings": "💸",
+ "monkey": "🐒",
+ "monkey_face": "🐵",
+ "monorail": "🚝",
+ "moon_cake": "🥮",
+ "moon_viewing_ceremony": "🎑",
+ "mosque": "🕌",
+ "mosquito": "🦟",
+ "motor_boat": "🛥",
+ "motor_scooter": "🛵",
+ "motorcycle": "🏍",
+ "motorized_wheelchair": "🦼",
+ "motorway": "🛣",
+ "mount_fuji": "🗻",
+ "mountain": "⛰",
+ "mountain_cableway": "🚠",
+ "mountain_railway": "🚞",
+ "mouse": "🐭",
+ "mouse_face": "🐭",
+ "mouth": "👄",
+ "movie_camera": "🎥",
+ "mushroom": "🍄",
+ "musical_keyboard": "🎹",
+ "musical_note": "🎵",
+ "musical_notes": "🎶",
+ "musical_score": "🎼",
+ "muted_speaker": "🔇",
+ "nail_polish": "💅",
+ "nail_polish_dark_skin_tone": "💅🏿",
+ "nail_polish_light_skin_tone": "💅🏻",
+ "nail_polish_medium-dark_skin_tone": "💅🏾",
+ "nail_polish_medium-light_skin_tone": "💅🏼",
+ "nail_polish_medium_skin_tone": "💅🏽",
+ "name_badge": "📛",
+ "national_park": "🏞",
+ "nauseated_face": "🤢",
+ "nazar_amulet": "🧿",
+ "necktie": "👔",
+ "nerd_face": "🤓",
+ "neutral_face": "😐",
+ "new_moon": "🌑",
+ "new_moon_face": "🌚",
+ "newspaper": "📰",
+ "next_track_button": "⏭",
+ "night_with_stars": "🌃",
+ "nine-thirty": "🕤",
+ "nine_o’clock": "🕘",
+ "no_bicycles": "🚳",
+ "no_entry": "⛔",
+ "no_littering": "🚯",
+ "no_mobile_phones": "📵",
+ "no_one_under_eighteen": "🔞",
+ "no_pedestrians": "🚷",
+ "no_smoking": "🚭",
+ "non-potable_water": "🚱",
+ "nose": "👃",
+ "nose_dark_skin_tone": "👃🏿",
+ "nose_light_skin_tone": "👃🏻",
+ "nose_medium-dark_skin_tone": "👃🏾",
+ "nose_medium-light_skin_tone": "👃🏼",
+ "nose_medium_skin_tone": "👃🏽",
+ "notebook": "📓",
+ "notebook_with_decorative_cover": "📔",
+ "nut_and_bolt": "🔩",
+ "octopus": "🐙",
+ "oden": "🍢",
+ "office_building": "🏢",
+ "ogre": "👹",
+ "oil_drum": "🛢",
+ "old_key": "🗝",
+ "old_man": "👴",
+ "old_man_dark_skin_tone": "👴🏿",
+ "old_man_light_skin_tone": "👴🏻",
+ "old_man_medium-dark_skin_tone": "👴🏾",
+ "old_man_medium-light_skin_tone": "👴🏼",
+ "old_man_medium_skin_tone": "👴🏽",
+ "old_woman": "👵",
+ "old_woman_dark_skin_tone": "👵🏿",
+ "old_woman_light_skin_tone": "👵🏻",
+ "old_woman_medium-dark_skin_tone": "👵🏾",
+ "old_woman_medium-light_skin_tone": "👵🏼",
+ "old_woman_medium_skin_tone": "👵🏽",
+ "older_adult": "🧓",
+ "older_adult_dark_skin_tone": "🧓🏿",
+ "older_adult_light_skin_tone": "🧓🏻",
+ "older_adult_medium-dark_skin_tone": "🧓🏾",
+ "older_adult_medium-light_skin_tone": "🧓🏼",
+ "older_adult_medium_skin_tone": "🧓🏽",
+ "om": "🕉",
+ "oncoming_automobile": "🚘",
+ "oncoming_bus": "🚍",
+ "oncoming_fist": "👊",
+ "oncoming_fist_dark_skin_tone": "👊🏿",
+ "oncoming_fist_light_skin_tone": "👊🏻",
+ "oncoming_fist_medium-dark_skin_tone": "👊🏾",
+ "oncoming_fist_medium-light_skin_tone": "👊🏼",
+ "oncoming_fist_medium_skin_tone": "👊🏽",
+ "oncoming_police_car": "🚔",
+ "oncoming_taxi": "🚖",
+ "one-piece_swimsuit": "🩱",
+ "one-thirty": "🕜",
+ "one_o’clock": "🕐",
+ "onion": "🧅",
+ "open_book": "📖",
+ "open_file_folder": "📂",
+ "open_hands": "👐",
+ "open_hands_dark_skin_tone": "👐🏿",
+ "open_hands_light_skin_tone": "👐🏻",
+ "open_hands_medium-dark_skin_tone": "👐🏾",
+ "open_hands_medium-light_skin_tone": "👐🏼",
+ "open_hands_medium_skin_tone": "👐🏽",
+ "open_mailbox_with_lowered_flag": "📭",
+ "open_mailbox_with_raised_flag": "📬",
+ "optical_disk": "💿",
+ "orange_book": "📙",
+ "orange_circle": "🟠",
+ "orange_heart": "🧡",
+ "orange_square": "🟧",
+ "orangutan": "🦧",
+ "orthodox_cross": "☦",
+ "otter": "🦦",
+ "outbox_tray": "📤",
+ "owl": "🦉",
+ "ox": "🐂",
+ "oyster": "🦪",
+ "package": "📦",
+ "page_facing_up": "📄",
+ "page_with_curl": "📃",
+ "pager": "📟",
+ "paintbrush": "🖌",
+ "palm_tree": "🌴",
+ "palms_up_together": "🤲",
+ "palms_up_together_dark_skin_tone": "🤲🏿",
+ "palms_up_together_light_skin_tone": "🤲🏻",
+ "palms_up_together_medium-dark_skin_tone": "🤲🏾",
+ "palms_up_together_medium-light_skin_tone": "🤲🏼",
+ "palms_up_together_medium_skin_tone": "🤲🏽",
+ "pancakes": "🥞",
+ "panda_face": "🐼",
+ "paperclip": "📎",
+ "parrot": "🦜",
+ "part_alternation_mark": "〽",
+ "party_popper": "🎉",
+ "partying_face": "🥳",
+ "passenger_ship": "🛳",
+ "passport_control": "🛂",
+ "pause_button": "⏸",
+ "paw_prints": "🐾",
+ "peace_symbol": "☮",
+ "peach": "🍑",
+ "peacock": "🦚",
+ "peanuts": "🥜",
+ "pear": "🍐",
+ "pen": "🖊",
+ "pencil": "📝",
+ "penguin": "🐧",
+ "pensive_face": "😔",
+ "people_holding_hands": "🧑\u200d🤝\u200d🧑",
+ "people_with_bunny_ears": "👯",
+ "people_wrestling": "🤼",
+ "performing_arts": "🎭",
+ "persevering_face": "😣",
+ "person_biking": "🚴",
+ "person_biking_dark_skin_tone": "🚴🏿",
+ "person_biking_light_skin_tone": "🚴🏻",
+ "person_biking_medium-dark_skin_tone": "🚴🏾",
+ "person_biking_medium-light_skin_tone": "🚴🏼",
+ "person_biking_medium_skin_tone": "🚴🏽",
+ "person_bouncing_ball": "⛹",
+ "person_bouncing_ball_dark_skin_tone": "⛹🏿",
+ "person_bouncing_ball_light_skin_tone": "⛹🏻",
+ "person_bouncing_ball_medium-dark_skin_tone": "⛹🏾",
+ "person_bouncing_ball_medium-light_skin_tone": "⛹🏼",
+ "person_bouncing_ball_medium_skin_tone": "⛹🏽",
+ "person_bowing": "🙇",
+ "person_bowing_dark_skin_tone": "🙇🏿",
+ "person_bowing_light_skin_tone": "🙇🏻",
+ "person_bowing_medium-dark_skin_tone": "🙇🏾",
+ "person_bowing_medium-light_skin_tone": "🙇🏼",
+ "person_bowing_medium_skin_tone": "🙇🏽",
+ "person_cartwheeling": "🤸",
+ "person_cartwheeling_dark_skin_tone": "🤸🏿",
+ "person_cartwheeling_light_skin_tone": "🤸🏻",
+ "person_cartwheeling_medium-dark_skin_tone": "🤸🏾",
+ "person_cartwheeling_medium-light_skin_tone": "🤸🏼",
+ "person_cartwheeling_medium_skin_tone": "🤸🏽",
+ "person_climbing": "🧗",
+ "person_climbing_dark_skin_tone": "🧗🏿",
+ "person_climbing_light_skin_tone": "🧗🏻",
+ "person_climbing_medium-dark_skin_tone": "🧗🏾",
+ "person_climbing_medium-light_skin_tone": "🧗🏼",
+ "person_climbing_medium_skin_tone": "🧗🏽",
+ "person_facepalming": "🤦",
+ "person_facepalming_dark_skin_tone": "🤦🏿",
+ "person_facepalming_light_skin_tone": "🤦🏻",
+ "person_facepalming_medium-dark_skin_tone": "🤦🏾",
+ "person_facepalming_medium-light_skin_tone": "🤦🏼",
+ "person_facepalming_medium_skin_tone": "🤦🏽",
+ "person_fencing": "🤺",
+ "person_frowning": "🙍",
+ "person_frowning_dark_skin_tone": "🙍🏿",
+ "person_frowning_light_skin_tone": "🙍🏻",
+ "person_frowning_medium-dark_skin_tone": "🙍🏾",
+ "person_frowning_medium-light_skin_tone": "🙍🏼",
+ "person_frowning_medium_skin_tone": "🙍🏽",
+ "person_gesturing_no": "🙅",
+ "person_gesturing_no_dark_skin_tone": "🙅🏿",
+ "person_gesturing_no_light_skin_tone": "🙅🏻",
+ "person_gesturing_no_medium-dark_skin_tone": "🙅🏾",
+ "person_gesturing_no_medium-light_skin_tone": "🙅🏼",
+ "person_gesturing_no_medium_skin_tone": "🙅🏽",
+ "person_gesturing_ok": "🙆",
+ "person_gesturing_ok_dark_skin_tone": "🙆🏿",
+ "person_gesturing_ok_light_skin_tone": "🙆🏻",
+ "person_gesturing_ok_medium-dark_skin_tone": "🙆🏾",
+ "person_gesturing_ok_medium-light_skin_tone": "🙆🏼",
+ "person_gesturing_ok_medium_skin_tone": "🙆🏽",
+ "person_getting_haircut": "💇",
+ "person_getting_haircut_dark_skin_tone": "💇🏿",
+ "person_getting_haircut_light_skin_tone": "💇🏻",
+ "person_getting_haircut_medium-dark_skin_tone": "💇🏾",
+ "person_getting_haircut_medium-light_skin_tone": "💇🏼",
+ "person_getting_haircut_medium_skin_tone": "💇🏽",
+ "person_getting_massage": "💆",
+ "person_getting_massage_dark_skin_tone": "💆🏿",
+ "person_getting_massage_light_skin_tone": "💆🏻",
+ "person_getting_massage_medium-dark_skin_tone": "💆🏾",
+ "person_getting_massage_medium-light_skin_tone": "💆🏼",
+ "person_getting_massage_medium_skin_tone": "💆🏽",
+ "person_golfing": "🏌",
+ "person_golfing_dark_skin_tone": "🏌🏿",
+ "person_golfing_light_skin_tone": "🏌🏻",
+ "person_golfing_medium-dark_skin_tone": "🏌🏾",
+ "person_golfing_medium-light_skin_tone": "🏌🏼",
+ "person_golfing_medium_skin_tone": "🏌🏽",
+ "person_in_bed": "🛌",
+ "person_in_bed_dark_skin_tone": "🛌🏿",
+ "person_in_bed_light_skin_tone": "🛌🏻",
+ "person_in_bed_medium-dark_skin_tone": "🛌🏾",
+ "person_in_bed_medium-light_skin_tone": "🛌🏼",
+ "person_in_bed_medium_skin_tone": "🛌🏽",
+ "person_in_lotus_position": "🧘",
+ "person_in_lotus_position_dark_skin_tone": "🧘🏿",
+ "person_in_lotus_position_light_skin_tone": "🧘🏻",
+ "person_in_lotus_position_medium-dark_skin_tone": "🧘🏾",
+ "person_in_lotus_position_medium-light_skin_tone": "🧘🏼",
+ "person_in_lotus_position_medium_skin_tone": "🧘🏽",
+ "person_in_steamy_room": "🧖",
+ "person_in_steamy_room_dark_skin_tone": "🧖🏿",
+ "person_in_steamy_room_light_skin_tone": "🧖🏻",
+ "person_in_steamy_room_medium-dark_skin_tone": "🧖🏾",
+ "person_in_steamy_room_medium-light_skin_tone": "🧖🏼",
+ "person_in_steamy_room_medium_skin_tone": "🧖🏽",
+ "person_juggling": "🤹",
+ "person_juggling_dark_skin_tone": "🤹🏿",
+ "person_juggling_light_skin_tone": "🤹🏻",
+ "person_juggling_medium-dark_skin_tone": "🤹🏾",
+ "person_juggling_medium-light_skin_tone": "🤹🏼",
+ "person_juggling_medium_skin_tone": "🤹🏽",
+ "person_kneeling": "🧎",
+ "person_lifting_weights": "🏋",
+ "person_lifting_weights_dark_skin_tone": "🏋🏿",
+ "person_lifting_weights_light_skin_tone": "🏋🏻",
+ "person_lifting_weights_medium-dark_skin_tone": "🏋🏾",
+ "person_lifting_weights_medium-light_skin_tone": "🏋🏼",
+ "person_lifting_weights_medium_skin_tone": "🏋🏽",
+ "person_mountain_biking": "🚵",
+ "person_mountain_biking_dark_skin_tone": "🚵🏿",
+ "person_mountain_biking_light_skin_tone": "🚵🏻",
+ "person_mountain_biking_medium-dark_skin_tone": "🚵🏾",
+ "person_mountain_biking_medium-light_skin_tone": "🚵🏼",
+ "person_mountain_biking_medium_skin_tone": "🚵🏽",
+ "person_playing_handball": "🤾",
+ "person_playing_handball_dark_skin_tone": "🤾🏿",
+ "person_playing_handball_light_skin_tone": "🤾🏻",
+ "person_playing_handball_medium-dark_skin_tone": "🤾🏾",
+ "person_playing_handball_medium-light_skin_tone": "🤾🏼",
+ "person_playing_handball_medium_skin_tone": "🤾🏽",
+ "person_playing_water_polo": "🤽",
+ "person_playing_water_polo_dark_skin_tone": "🤽🏿",
+ "person_playing_water_polo_light_skin_tone": "🤽🏻",
+ "person_playing_water_polo_medium-dark_skin_tone": "🤽🏾",
+ "person_playing_water_polo_medium-light_skin_tone": "🤽🏼",
+ "person_playing_water_polo_medium_skin_tone": "🤽🏽",
+ "person_pouting": "🙎",
+ "person_pouting_dark_skin_tone": "🙎🏿",
+ "person_pouting_light_skin_tone": "🙎🏻",
+ "person_pouting_medium-dark_skin_tone": "🙎🏾",
+ "person_pouting_medium-light_skin_tone": "🙎🏼",
+ "person_pouting_medium_skin_tone": "🙎🏽",
+ "person_raising_hand": "🙋",
+ "person_raising_hand_dark_skin_tone": "🙋🏿",
+ "person_raising_hand_light_skin_tone": "🙋🏻",
+ "person_raising_hand_medium-dark_skin_tone": "🙋🏾",
+ "person_raising_hand_medium-light_skin_tone": "🙋🏼",
+ "person_raising_hand_medium_skin_tone": "🙋🏽",
+ "person_rowing_boat": "🚣",
+ "person_rowing_boat_dark_skin_tone": "🚣🏿",
+ "person_rowing_boat_light_skin_tone": "🚣🏻",
+ "person_rowing_boat_medium-dark_skin_tone": "🚣🏾",
+ "person_rowing_boat_medium-light_skin_tone": "🚣🏼",
+ "person_rowing_boat_medium_skin_tone": "🚣🏽",
+ "person_running": "🏃",
+ "person_running_dark_skin_tone": "🏃🏿",
+ "person_running_light_skin_tone": "🏃🏻",
+ "person_running_medium-dark_skin_tone": "🏃🏾",
+ "person_running_medium-light_skin_tone": "🏃🏼",
+ "person_running_medium_skin_tone": "🏃🏽",
+ "person_shrugging": "🤷",
+ "person_shrugging_dark_skin_tone": "🤷🏿",
+ "person_shrugging_light_skin_tone": "🤷🏻",
+ "person_shrugging_medium-dark_skin_tone": "🤷🏾",
+ "person_shrugging_medium-light_skin_tone": "🤷🏼",
+ "person_shrugging_medium_skin_tone": "🤷🏽",
+ "person_standing": "🧍",
+ "person_surfing": "🏄",
+ "person_surfing_dark_skin_tone": "🏄🏿",
+ "person_surfing_light_skin_tone": "🏄🏻",
+ "person_surfing_medium-dark_skin_tone": "🏄🏾",
+ "person_surfing_medium-light_skin_tone": "🏄🏼",
+ "person_surfing_medium_skin_tone": "🏄🏽",
+ "person_swimming": "🏊",
+ "person_swimming_dark_skin_tone": "🏊🏿",
+ "person_swimming_light_skin_tone": "🏊🏻",
+ "person_swimming_medium-dark_skin_tone": "🏊🏾",
+ "person_swimming_medium-light_skin_tone": "🏊🏼",
+ "person_swimming_medium_skin_tone": "🏊🏽",
+ "person_taking_bath": "🛀",
+ "person_taking_bath_dark_skin_tone": "🛀🏿",
+ "person_taking_bath_light_skin_tone": "🛀🏻",
+ "person_taking_bath_medium-dark_skin_tone": "🛀🏾",
+ "person_taking_bath_medium-light_skin_tone": "🛀🏼",
+ "person_taking_bath_medium_skin_tone": "🛀🏽",
+ "person_tipping_hand": "💁",
+ "person_tipping_hand_dark_skin_tone": "💁🏿",
+ "person_tipping_hand_light_skin_tone": "💁🏻",
+ "person_tipping_hand_medium-dark_skin_tone": "💁🏾",
+ "person_tipping_hand_medium-light_skin_tone": "💁🏼",
+ "person_tipping_hand_medium_skin_tone": "💁🏽",
+ "person_walking": "🚶",
+ "person_walking_dark_skin_tone": "🚶🏿",
+ "person_walking_light_skin_tone": "🚶🏻",
+ "person_walking_medium-dark_skin_tone": "🚶🏾",
+ "person_walking_medium-light_skin_tone": "🚶🏼",
+ "person_walking_medium_skin_tone": "🚶🏽",
+ "person_wearing_turban": "👳",
+ "person_wearing_turban_dark_skin_tone": "👳🏿",
+ "person_wearing_turban_light_skin_tone": "👳🏻",
+ "person_wearing_turban_medium-dark_skin_tone": "👳🏾",
+ "person_wearing_turban_medium-light_skin_tone": "👳🏼",
+ "person_wearing_turban_medium_skin_tone": "👳🏽",
+ "petri_dish": "🧫",
+ "pick": "⛏",
+ "pie": "🥧",
+ "pig": "🐷",
+ "pig_face": "🐷",
+ "pig_nose": "🐽",
+ "pile_of_poo": "💩",
+ "pill": "💊",
+ "pinching_hand": "🤏",
+ "pine_decoration": "🎍",
+ "pineapple": "🍍",
+ "ping_pong": "🏓",
+ "pirate_flag": "🏴\u200d☠️",
+ "pistol": "🔫",
+ "pizza": "🍕",
+ "place_of_worship": "🛐",
+ "play_button": "▶",
+ "play_or_pause_button": "⏯",
+ "pleading_face": "🥺",
+ "police_car": "🚓",
+ "police_car_light": "🚨",
+ "police_officer": "👮",
+ "police_officer_dark_skin_tone": "👮🏿",
+ "police_officer_light_skin_tone": "👮🏻",
+ "police_officer_medium-dark_skin_tone": "👮🏾",
+ "police_officer_medium-light_skin_tone": "👮🏼",
+ "police_officer_medium_skin_tone": "👮🏽",
+ "poodle": "🐩",
+ "pool_8_ball": "🎱",
+ "popcorn": "🍿",
+ "post_office": "🏣",
+ "postal_horn": "📯",
+ "postbox": "📮",
+ "pot_of_food": "🍲",
+ "potable_water": "🚰",
+ "potato": "🥔",
+ "poultry_leg": "🍗",
+ "pound_banknote": "💷",
+ "pouting_cat_face": "😾",
+ "pouting_face": "😡",
+ "prayer_beads": "📿",
+ "pregnant_woman": "🤰",
+ "pregnant_woman_dark_skin_tone": "🤰🏿",
+ "pregnant_woman_light_skin_tone": "🤰🏻",
+ "pregnant_woman_medium-dark_skin_tone": "🤰🏾",
+ "pregnant_woman_medium-light_skin_tone": "🤰🏼",
+ "pregnant_woman_medium_skin_tone": "🤰🏽",
+ "pretzel": "🥨",
+ "probing_cane": "🦯",
+ "prince": "🤴",
+ "prince_dark_skin_tone": "🤴🏿",
+ "prince_light_skin_tone": "🤴🏻",
+ "prince_medium-dark_skin_tone": "🤴🏾",
+ "prince_medium-light_skin_tone": "🤴🏼",
+ "prince_medium_skin_tone": "🤴🏽",
+ "princess": "👸",
+ "princess_dark_skin_tone": "👸🏿",
+ "princess_light_skin_tone": "👸🏻",
+ "princess_medium-dark_skin_tone": "👸🏾",
+ "princess_medium-light_skin_tone": "👸🏼",
+ "princess_medium_skin_tone": "👸🏽",
+ "printer": "🖨",
+ "prohibited": "🚫",
+ "purple_circle": "🟣",
+ "purple_heart": "💜",
+ "purple_square": "🟪",
+ "purse": "👛",
+ "pushpin": "📌",
+ "question_mark": "❓",
+ "rabbit": "🐰",
+ "rabbit_face": "🐰",
+ "raccoon": "🦝",
+ "racing_car": "🏎",
+ "radio": "📻",
+ "radio_button": "🔘",
+ "radioactive": "☢",
+ "railway_car": "🚃",
+ "railway_track": "🛤",
+ "rainbow": "🌈",
+ "rainbow_flag": "🏳️\u200d🌈",
+ "raised_back_of_hand": "🤚",
+ "raised_back_of_hand_dark_skin_tone": "🤚🏿",
+ "raised_back_of_hand_light_skin_tone": "🤚🏻",
+ "raised_back_of_hand_medium-dark_skin_tone": "🤚🏾",
+ "raised_back_of_hand_medium-light_skin_tone": "🤚🏼",
+ "raised_back_of_hand_medium_skin_tone": "🤚🏽",
+ "raised_fist": "✊",
+ "raised_fist_dark_skin_tone": "✊🏿",
+ "raised_fist_light_skin_tone": "✊🏻",
+ "raised_fist_medium-dark_skin_tone": "✊🏾",
+ "raised_fist_medium-light_skin_tone": "✊🏼",
+ "raised_fist_medium_skin_tone": "✊🏽",
+ "raised_hand": "✋",
+ "raised_hand_dark_skin_tone": "✋🏿",
+ "raised_hand_light_skin_tone": "✋🏻",
+ "raised_hand_medium-dark_skin_tone": "✋🏾",
+ "raised_hand_medium-light_skin_tone": "✋🏼",
+ "raised_hand_medium_skin_tone": "✋🏽",
+ "raising_hands": "🙌",
+ "raising_hands_dark_skin_tone": "🙌🏿",
+ "raising_hands_light_skin_tone": "🙌🏻",
+ "raising_hands_medium-dark_skin_tone": "🙌🏾",
+ "raising_hands_medium-light_skin_tone": "🙌🏼",
+ "raising_hands_medium_skin_tone": "🙌🏽",
+ "ram": "🐏",
+ "rat": "🐀",
+ "razor": "🪒",
+ "ringed_planet": "🪐",
+ "receipt": "🧾",
+ "record_button": "⏺",
+ "recycling_symbol": "♻",
+ "red_apple": "🍎",
+ "red_circle": "🔴",
+ "red_envelope": "🧧",
+ "red_hair": "🦰",
+ "red-haired_man": "👨\u200d🦰",
+ "red-haired_woman": "👩\u200d🦰",
+ "red_heart": "❤",
+ "red_paper_lantern": "🏮",
+ "red_square": "🟥",
+ "red_triangle_pointed_down": "🔻",
+ "red_triangle_pointed_up": "🔺",
+ "registered": "®",
+ "relieved_face": "😌",
+ "reminder_ribbon": "🎗",
+ "repeat_button": "🔁",
+ "repeat_single_button": "🔂",
+ "rescue_worker’s_helmet": "⛑",
+ "restroom": "🚻",
+ "reverse_button": "◀",
+ "revolving_hearts": "💞",
+ "rhinoceros": "🦏",
+ "ribbon": "🎀",
+ "rice_ball": "🍙",
+ "rice_cracker": "🍘",
+ "right-facing_fist": "🤜",
+ "right-facing_fist_dark_skin_tone": "🤜🏿",
+ "right-facing_fist_light_skin_tone": "🤜🏻",
+ "right-facing_fist_medium-dark_skin_tone": "🤜🏾",
+ "right-facing_fist_medium-light_skin_tone": "🤜🏼",
+ "right-facing_fist_medium_skin_tone": "🤜🏽",
+ "right_anger_bubble": "🗯",
+ "right_arrow": "➡",
+ "right_arrow_curving_down": "⤵",
+ "right_arrow_curving_left": "↩",
+ "right_arrow_curving_up": "⤴",
+ "ring": "💍",
+ "roasted_sweet_potato": "🍠",
+ "robot_face": "🤖",
+ "rocket": "🚀",
+ "roll_of_paper": "🧻",
+ "rolled-up_newspaper": "🗞",
+ "roller_coaster": "🎢",
+ "rolling_on_the_floor_laughing": "🤣",
+ "rooster": "🐓",
+ "rose": "🌹",
+ "rosette": "🏵",
+ "round_pushpin": "📍",
+ "rugby_football": "🏉",
+ "running_shirt": "🎽",
+ "running_shoe": "👟",
+ "sad_but_relieved_face": "😥",
+ "safety_pin": "🧷",
+ "safety_vest": "🦺",
+ "salt": "🧂",
+ "sailboat": "⛵",
+ "sake": "🍶",
+ "sandwich": "🥪",
+ "sari": "🥻",
+ "satellite": "📡",
+ "satellite_antenna": "📡",
+ "sauropod": "🦕",
+ "saxophone": "🎷",
+ "scarf": "🧣",
+ "school": "🏫",
+ "school_backpack": "🎒",
+ "scissors": "✂",
+ "scorpion": "🦂",
+ "scroll": "📜",
+ "seat": "💺",
+ "see-no-evil_monkey": "🙈",
+ "seedling": "🌱",
+ "selfie": "🤳",
+ "selfie_dark_skin_tone": "🤳🏿",
+ "selfie_light_skin_tone": "🤳🏻",
+ "selfie_medium-dark_skin_tone": "🤳🏾",
+ "selfie_medium-light_skin_tone": "🤳🏼",
+ "selfie_medium_skin_tone": "🤳🏽",
+ "service_dog": "🐕\u200d🦺",
+ "seven-thirty": "🕢",
+ "seven_o’clock": "🕖",
+ "shallow_pan_of_food": "🥘",
+ "shamrock": "☘",
+ "shark": "🦈",
+ "shaved_ice": "🍧",
+ "sheaf_of_rice": "🌾",
+ "shield": "🛡",
+ "shinto_shrine": "⛩",
+ "ship": "🚢",
+ "shooting_star": "🌠",
+ "shopping_bags": "🛍",
+ "shopping_cart": "🛒",
+ "shortcake": "🍰",
+ "shorts": "🩳",
+ "shower": "🚿",
+ "shrimp": "🦐",
+ "shuffle_tracks_button": "🔀",
+ "shushing_face": "🤫",
+ "sign_of_the_horns": "🤘",
+ "sign_of_the_horns_dark_skin_tone": "🤘🏿",
+ "sign_of_the_horns_light_skin_tone": "🤘🏻",
+ "sign_of_the_horns_medium-dark_skin_tone": "🤘🏾",
+ "sign_of_the_horns_medium-light_skin_tone": "🤘🏼",
+ "sign_of_the_horns_medium_skin_tone": "🤘🏽",
+ "six-thirty": "🕡",
+ "six_o’clock": "🕕",
+ "skateboard": "🛹",
+ "skier": "⛷",
+ "skis": "🎿",
+ "skull": "💀",
+ "skull_and_crossbones": "☠",
+ "skunk": "🦨",
+ "sled": "🛷",
+ "sleeping_face": "😴",
+ "sleepy_face": "😪",
+ "slightly_frowning_face": "🙁",
+ "slightly_smiling_face": "🙂",
+ "slot_machine": "🎰",
+ "sloth": "🦥",
+ "small_airplane": "🛩",
+ "small_blue_diamond": "🔹",
+ "small_orange_diamond": "🔸",
+ "smiling_cat_face_with_heart-eyes": "😻",
+ "smiling_face": "☺",
+ "smiling_face_with_halo": "😇",
+ "smiling_face_with_3_hearts": "🥰",
+ "smiling_face_with_heart-eyes": "😍",
+ "smiling_face_with_horns": "😈",
+ "smiling_face_with_smiling_eyes": "😊",
+ "smiling_face_with_sunglasses": "😎",
+ "smirking_face": "😏",
+ "snail": "🐌",
+ "snake": "🐍",
+ "sneezing_face": "🤧",
+ "snow-capped_mountain": "🏔",
+ "snowboarder": "🏂",
+ "snowboarder_dark_skin_tone": "🏂🏿",
+ "snowboarder_light_skin_tone": "🏂🏻",
+ "snowboarder_medium-dark_skin_tone": "🏂🏾",
+ "snowboarder_medium-light_skin_tone": "🏂🏼",
+ "snowboarder_medium_skin_tone": "🏂🏽",
+ "snowflake": "❄",
+ "snowman": "☃",
+ "snowman_without_snow": "⛄",
+ "soap": "🧼",
+ "soccer_ball": "⚽",
+ "socks": "🧦",
+ "softball": "🥎",
+ "soft_ice_cream": "🍦",
+ "spade_suit": "♠",
+ "spaghetti": "🍝",
+ "sparkle": "❇",
+ "sparkler": "🎇",
+ "sparkles": "✨",
+ "sparkling_heart": "💖",
+ "speak-no-evil_monkey": "🙊",
+ "speaker_high_volume": "🔊",
+ "speaker_low_volume": "🔈",
+ "speaker_medium_volume": "🔉",
+ "speaking_head": "🗣",
+ "speech_balloon": "💬",
+ "speedboat": "🚤",
+ "spider": "🕷",
+ "spider_web": "🕸",
+ "spiral_calendar": "🗓",
+ "spiral_notepad": "🗒",
+ "spiral_shell": "🐚",
+ "spoon": "🥄",
+ "sponge": "🧽",
+ "sport_utility_vehicle": "🚙",
+ "sports_medal": "🏅",
+ "spouting_whale": "🐳",
+ "squid": "🦑",
+ "squinting_face_with_tongue": "😝",
+ "stadium": "🏟",
+ "star-struck": "🤩",
+ "star_and_crescent": "☪",
+ "star_of_david": "✡",
+ "station": "🚉",
+ "steaming_bowl": "🍜",
+ "stethoscope": "🩺",
+ "stop_button": "⏹",
+ "stop_sign": "🛑",
+ "stopwatch": "⏱",
+ "straight_ruler": "📏",
+ "strawberry": "🍓",
+ "studio_microphone": "🎙",
+ "stuffed_flatbread": "🥙",
+ "sun": "☀",
+ "sun_behind_cloud": "⛅",
+ "sun_behind_large_cloud": "🌥",
+ "sun_behind_rain_cloud": "🌦",
+ "sun_behind_small_cloud": "🌤",
+ "sun_with_face": "🌞",
+ "sunflower": "🌻",
+ "sunglasses": "😎",
+ "sunrise": "🌅",
+ "sunrise_over_mountains": "🌄",
+ "sunset": "🌇",
+ "superhero": "🦸",
+ "supervillain": "🦹",
+ "sushi": "🍣",
+ "suspension_railway": "🚟",
+ "swan": "🦢",
+ "sweat_droplets": "💦",
+ "synagogue": "🕍",
+ "syringe": "💉",
+ "t-shirt": "👕",
+ "taco": "🌮",
+ "takeout_box": "🥡",
+ "tanabata_tree": "🎋",
+ "tangerine": "🍊",
+ "taxi": "🚕",
+ "teacup_without_handle": "🍵",
+ "tear-off_calendar": "📆",
+ "teddy_bear": "🧸",
+ "telephone": "☎",
+ "telephone_receiver": "📞",
+ "telescope": "🔭",
+ "television": "📺",
+ "ten-thirty": "🕥",
+ "ten_o’clock": "🕙",
+ "tennis": "🎾",
+ "tent": "⛺",
+ "test_tube": "🧪",
+ "thermometer": "🌡",
+ "thinking_face": "🤔",
+ "thought_balloon": "💭",
+ "thread": "🧵",
+ "three-thirty": "🕞",
+ "three_o’clock": "🕒",
+ "thumbs_down": "👎",
+ "thumbs_down_dark_skin_tone": "👎🏿",
+ "thumbs_down_light_skin_tone": "👎🏻",
+ "thumbs_down_medium-dark_skin_tone": "👎🏾",
+ "thumbs_down_medium-light_skin_tone": "👎🏼",
+ "thumbs_down_medium_skin_tone": "👎🏽",
+ "thumbs_up": "👍",
+ "thumbs_up_dark_skin_tone": "👍🏿",
+ "thumbs_up_light_skin_tone": "👍🏻",
+ "thumbs_up_medium-dark_skin_tone": "👍🏾",
+ "thumbs_up_medium-light_skin_tone": "👍🏼",
+ "thumbs_up_medium_skin_tone": "👍🏽",
+ "ticket": "🎫",
+ "tiger": "🐯",
+ "tiger_face": "🐯",
+ "timer_clock": "⏲",
+ "tired_face": "😫",
+ "toolbox": "🧰",
+ "toilet": "🚽",
+ "tomato": "🍅",
+ "tongue": "👅",
+ "tooth": "🦷",
+ "top_hat": "🎩",
+ "tornado": "🌪",
+ "trackball": "🖲",
+ "tractor": "🚜",
+ "trade_mark": "™",
+ "train": "🚋",
+ "tram": "🚊",
+ "tram_car": "🚋",
+ "triangular_flag": "🚩",
+ "triangular_ruler": "📐",
+ "trident_emblem": "🔱",
+ "trolleybus": "🚎",
+ "trophy": "🏆",
+ "tropical_drink": "🍹",
+ "tropical_fish": "🐠",
+ "trumpet": "🎺",
+ "tulip": "🌷",
+ "tumbler_glass": "🥃",
+ "turtle": "🐢",
+ "twelve-thirty": "🕧",
+ "twelve_o’clock": "🕛",
+ "two-hump_camel": "🐫",
+ "two-thirty": "🕝",
+ "two_hearts": "💕",
+ "two_men_holding_hands": "👬",
+ "two_o’clock": "🕑",
+ "two_women_holding_hands": "👭",
+ "umbrella": "☂",
+ "umbrella_on_ground": "⛱",
+ "umbrella_with_rain_drops": "☔",
+ "unamused_face": "😒",
+ "unicorn_face": "🦄",
+ "unlocked": "🔓",
+ "up-down_arrow": "↕",
+ "up-left_arrow": "↖",
+ "up-right_arrow": "↗",
+ "up_arrow": "⬆",
+ "upside-down_face": "🙃",
+ "upwards_button": "🔼",
+ "vampire": "🧛",
+ "vampire_dark_skin_tone": "🧛🏿",
+ "vampire_light_skin_tone": "🧛🏻",
+ "vampire_medium-dark_skin_tone": "🧛🏾",
+ "vampire_medium-light_skin_tone": "🧛🏼",
+ "vampire_medium_skin_tone": "🧛🏽",
+ "vertical_traffic_light": "🚦",
+ "vibration_mode": "📳",
+ "victory_hand": "✌",
+ "victory_hand_dark_skin_tone": "✌🏿",
+ "victory_hand_light_skin_tone": "✌🏻",
+ "victory_hand_medium-dark_skin_tone": "✌🏾",
+ "victory_hand_medium-light_skin_tone": "✌🏼",
+ "victory_hand_medium_skin_tone": "✌🏽",
+ "video_camera": "📹",
+ "video_game": "🎮",
+ "videocassette": "📼",
+ "violin": "🎻",
+ "volcano": "🌋",
+ "volleyball": "🏐",
+ "vulcan_salute": "🖖",
+ "vulcan_salute_dark_skin_tone": "🖖🏿",
+ "vulcan_salute_light_skin_tone": "🖖🏻",
+ "vulcan_salute_medium-dark_skin_tone": "🖖🏾",
+ "vulcan_salute_medium-light_skin_tone": "🖖🏼",
+ "vulcan_salute_medium_skin_tone": "🖖🏽",
+ "waffle": "🧇",
+ "waning_crescent_moon": "🌘",
+ "waning_gibbous_moon": "🌖",
+ "warning": "⚠",
+ "wastebasket": "🗑",
+ "watch": "⌚",
+ "water_buffalo": "🐃",
+ "water_closet": "🚾",
+ "water_wave": "🌊",
+ "watermelon": "🍉",
+ "waving_hand": "👋",
+ "waving_hand_dark_skin_tone": "👋🏿",
+ "waving_hand_light_skin_tone": "👋🏻",
+ "waving_hand_medium-dark_skin_tone": "👋🏾",
+ "waving_hand_medium-light_skin_tone": "👋🏼",
+ "waving_hand_medium_skin_tone": "👋🏽",
+ "wavy_dash": "〰",
+ "waxing_crescent_moon": "🌒",
+ "waxing_gibbous_moon": "🌔",
+ "weary_cat_face": "🙀",
+ "weary_face": "😩",
+ "wedding": "💒",
+ "whale": "🐳",
+ "wheel_of_dharma": "☸",
+ "wheelchair_symbol": "♿",
+ "white_circle": "⚪",
+ "white_exclamation_mark": "❕",
+ "white_flag": "🏳",
+ "white_flower": "💮",
+ "white_hair": "🦳",
+ "white-haired_man": "👨\u200d🦳",
+ "white-haired_woman": "👩\u200d🦳",
+ "white_heart": "🤍",
+ "white_heavy_check_mark": "✅",
+ "white_large_square": "⬜",
+ "white_medium-small_square": "◽",
+ "white_medium_square": "◻",
+ "white_medium_star": "⭐",
+ "white_question_mark": "❔",
+ "white_small_square": "▫",
+ "white_square_button": "🔳",
+ "wilted_flower": "🥀",
+ "wind_chime": "🎐",
+ "wind_face": "🌬",
+ "wine_glass": "🍷",
+ "winking_face": "😉",
+ "winking_face_with_tongue": "😜",
+ "wolf_face": "🐺",
+ "woman": "👩",
+ "woman_artist": "👩\u200d🎨",
+ "woman_artist_dark_skin_tone": "👩🏿\u200d🎨",
+ "woman_artist_light_skin_tone": "👩🏻\u200d🎨",
+ "woman_artist_medium-dark_skin_tone": "👩🏾\u200d🎨",
+ "woman_artist_medium-light_skin_tone": "👩🏼\u200d🎨",
+ "woman_artist_medium_skin_tone": "👩🏽\u200d🎨",
+ "woman_astronaut": "👩\u200d🚀",
+ "woman_astronaut_dark_skin_tone": "👩🏿\u200d🚀",
+ "woman_astronaut_light_skin_tone": "👩🏻\u200d🚀",
+ "woman_astronaut_medium-dark_skin_tone": "👩🏾\u200d🚀",
+ "woman_astronaut_medium-light_skin_tone": "👩🏼\u200d🚀",
+ "woman_astronaut_medium_skin_tone": "👩🏽\u200d🚀",
+ "woman_biking": "🚴\u200d♀️",
+ "woman_biking_dark_skin_tone": "🚴🏿\u200d♀️",
+ "woman_biking_light_skin_tone": "🚴🏻\u200d♀️",
+ "woman_biking_medium-dark_skin_tone": "🚴🏾\u200d♀️",
+ "woman_biking_medium-light_skin_tone": "🚴🏼\u200d♀️",
+ "woman_biking_medium_skin_tone": "🚴🏽\u200d♀️",
+ "woman_bouncing_ball": "⛹️\u200d♀️",
+ "woman_bouncing_ball_dark_skin_tone": "⛹🏿\u200d♀️",
+ "woman_bouncing_ball_light_skin_tone": "⛹🏻\u200d♀️",
+ "woman_bouncing_ball_medium-dark_skin_tone": "⛹🏾\u200d♀️",
+ "woman_bouncing_ball_medium-light_skin_tone": "⛹🏼\u200d♀️",
+ "woman_bouncing_ball_medium_skin_tone": "⛹🏽\u200d♀️",
+ "woman_bowing": "🙇\u200d♀️",
+ "woman_bowing_dark_skin_tone": "🙇🏿\u200d♀️",
+ "woman_bowing_light_skin_tone": "🙇🏻\u200d♀️",
+ "woman_bowing_medium-dark_skin_tone": "🙇🏾\u200d♀️",
+ "woman_bowing_medium-light_skin_tone": "🙇🏼\u200d♀️",
+ "woman_bowing_medium_skin_tone": "🙇🏽\u200d♀️",
+ "woman_cartwheeling": "🤸\u200d♀️",
+ "woman_cartwheeling_dark_skin_tone": "🤸🏿\u200d♀️",
+ "woman_cartwheeling_light_skin_tone": "🤸🏻\u200d♀️",
+ "woman_cartwheeling_medium-dark_skin_tone": "🤸🏾\u200d♀️",
+ "woman_cartwheeling_medium-light_skin_tone": "🤸🏼\u200d♀️",
+ "woman_cartwheeling_medium_skin_tone": "🤸🏽\u200d♀️",
+ "woman_climbing": "🧗\u200d♀️",
+ "woman_climbing_dark_skin_tone": "🧗🏿\u200d♀️",
+ "woman_climbing_light_skin_tone": "🧗🏻\u200d♀️",
+ "woman_climbing_medium-dark_skin_tone": "🧗🏾\u200d♀️",
+ "woman_climbing_medium-light_skin_tone": "🧗🏼\u200d♀️",
+ "woman_climbing_medium_skin_tone": "🧗🏽\u200d♀️",
+ "woman_construction_worker": "👷\u200d♀️",
+ "woman_construction_worker_dark_skin_tone": "👷🏿\u200d♀️",
+ "woman_construction_worker_light_skin_tone": "👷🏻\u200d♀️",
+ "woman_construction_worker_medium-dark_skin_tone": "👷🏾\u200d♀️",
+ "woman_construction_worker_medium-light_skin_tone": "👷🏼\u200d♀️",
+ "woman_construction_worker_medium_skin_tone": "👷🏽\u200d♀️",
+ "woman_cook": "👩\u200d🍳",
+ "woman_cook_dark_skin_tone": "👩🏿\u200d🍳",
+ "woman_cook_light_skin_tone": "👩🏻\u200d🍳",
+ "woman_cook_medium-dark_skin_tone": "👩🏾\u200d🍳",
+ "woman_cook_medium-light_skin_tone": "👩🏼\u200d🍳",
+ "woman_cook_medium_skin_tone": "👩🏽\u200d🍳",
+ "woman_dancing": "💃",
+ "woman_dancing_dark_skin_tone": "💃🏿",
+ "woman_dancing_light_skin_tone": "💃🏻",
+ "woman_dancing_medium-dark_skin_tone": "💃🏾",
+ "woman_dancing_medium-light_skin_tone": "💃🏼",
+ "woman_dancing_medium_skin_tone": "💃🏽",
+ "woman_dark_skin_tone": "👩🏿",
+ "woman_detective": "🕵️\u200d♀️",
+ "woman_detective_dark_skin_tone": "🕵🏿\u200d♀️",
+ "woman_detective_light_skin_tone": "🕵🏻\u200d♀️",
+ "woman_detective_medium-dark_skin_tone": "🕵🏾\u200d♀️",
+ "woman_detective_medium-light_skin_tone": "🕵🏼\u200d♀️",
+ "woman_detective_medium_skin_tone": "🕵🏽\u200d♀️",
+ "woman_elf": "🧝\u200d♀️",
+ "woman_elf_dark_skin_tone": "🧝🏿\u200d♀️",
+ "woman_elf_light_skin_tone": "🧝🏻\u200d♀️",
+ "woman_elf_medium-dark_skin_tone": "🧝🏾\u200d♀️",
+ "woman_elf_medium-light_skin_tone": "🧝🏼\u200d♀️",
+ "woman_elf_medium_skin_tone": "🧝🏽\u200d♀️",
+ "woman_facepalming": "🤦\u200d♀️",
+ "woman_facepalming_dark_skin_tone": "🤦🏿\u200d♀️",
+ "woman_facepalming_light_skin_tone": "🤦🏻\u200d♀️",
+ "woman_facepalming_medium-dark_skin_tone": "🤦🏾\u200d♀️",
+ "woman_facepalming_medium-light_skin_tone": "🤦🏼\u200d♀️",
+ "woman_facepalming_medium_skin_tone": "🤦🏽\u200d♀️",
+ "woman_factory_worker": "👩\u200d🏭",
+ "woman_factory_worker_dark_skin_tone": "👩🏿\u200d🏭",
+ "woman_factory_worker_light_skin_tone": "👩🏻\u200d🏭",
+ "woman_factory_worker_medium-dark_skin_tone": "👩🏾\u200d🏭",
+ "woman_factory_worker_medium-light_skin_tone": "👩🏼\u200d🏭",
+ "woman_factory_worker_medium_skin_tone": "👩🏽\u200d🏭",
+ "woman_fairy": "🧚\u200d♀️",
+ "woman_fairy_dark_skin_tone": "🧚🏿\u200d♀️",
+ "woman_fairy_light_skin_tone": "🧚🏻\u200d♀️",
+ "woman_fairy_medium-dark_skin_tone": "🧚🏾\u200d♀️",
+ "woman_fairy_medium-light_skin_tone": "🧚🏼\u200d♀️",
+ "woman_fairy_medium_skin_tone": "🧚🏽\u200d♀️",
+ "woman_farmer": "👩\u200d🌾",
+ "woman_farmer_dark_skin_tone": "👩🏿\u200d🌾",
+ "woman_farmer_light_skin_tone": "👩🏻\u200d🌾",
+ "woman_farmer_medium-dark_skin_tone": "👩🏾\u200d🌾",
+ "woman_farmer_medium-light_skin_tone": "👩🏼\u200d🌾",
+ "woman_farmer_medium_skin_tone": "👩🏽\u200d🌾",
+ "woman_firefighter": "👩\u200d🚒",
+ "woman_firefighter_dark_skin_tone": "👩🏿\u200d🚒",
+ "woman_firefighter_light_skin_tone": "👩🏻\u200d🚒",
+ "woman_firefighter_medium-dark_skin_tone": "👩🏾\u200d🚒",
+ "woman_firefighter_medium-light_skin_tone": "👩🏼\u200d🚒",
+ "woman_firefighter_medium_skin_tone": "👩🏽\u200d🚒",
+ "woman_frowning": "🙍\u200d♀️",
+ "woman_frowning_dark_skin_tone": "🙍🏿\u200d♀️",
+ "woman_frowning_light_skin_tone": "🙍🏻\u200d♀️",
+ "woman_frowning_medium-dark_skin_tone": "🙍🏾\u200d♀️",
+ "woman_frowning_medium-light_skin_tone": "🙍🏼\u200d♀️",
+ "woman_frowning_medium_skin_tone": "🙍🏽\u200d♀️",
+ "woman_genie": "🧞\u200d♀️",
+ "woman_gesturing_no": "🙅\u200d♀️",
+ "woman_gesturing_no_dark_skin_tone": "🙅🏿\u200d♀️",
+ "woman_gesturing_no_light_skin_tone": "🙅🏻\u200d♀️",
+ "woman_gesturing_no_medium-dark_skin_tone": "🙅🏾\u200d♀️",
+ "woman_gesturing_no_medium-light_skin_tone": "🙅🏼\u200d♀️",
+ "woman_gesturing_no_medium_skin_tone": "🙅🏽\u200d♀️",
+ "woman_gesturing_ok": "🙆\u200d♀️",
+ "woman_gesturing_ok_dark_skin_tone": "🙆🏿\u200d♀️",
+ "woman_gesturing_ok_light_skin_tone": "🙆🏻\u200d♀️",
+ "woman_gesturing_ok_medium-dark_skin_tone": "🙆🏾\u200d♀️",
+ "woman_gesturing_ok_medium-light_skin_tone": "🙆🏼\u200d♀️",
+ "woman_gesturing_ok_medium_skin_tone": "🙆🏽\u200d♀️",
+ "woman_getting_haircut": "💇\u200d♀️",
+ "woman_getting_haircut_dark_skin_tone": "💇🏿\u200d♀️",
+ "woman_getting_haircut_light_skin_tone": "💇🏻\u200d♀️",
+ "woman_getting_haircut_medium-dark_skin_tone": "💇🏾\u200d♀️",
+ "woman_getting_haircut_medium-light_skin_tone": "💇🏼\u200d♀️",
+ "woman_getting_haircut_medium_skin_tone": "💇🏽\u200d♀️",
+ "woman_getting_massage": "💆\u200d♀️",
+ "woman_getting_massage_dark_skin_tone": "💆🏿\u200d♀️",
+ "woman_getting_massage_light_skin_tone": "💆🏻\u200d♀️",
+ "woman_getting_massage_medium-dark_skin_tone": "💆🏾\u200d♀️",
+ "woman_getting_massage_medium-light_skin_tone": "💆🏼\u200d♀️",
+ "woman_getting_massage_medium_skin_tone": "💆🏽\u200d♀️",
+ "woman_golfing": "🏌️\u200d♀️",
+ "woman_golfing_dark_skin_tone": "🏌🏿\u200d♀️",
+ "woman_golfing_light_skin_tone": "🏌🏻\u200d♀️",
+ "woman_golfing_medium-dark_skin_tone": "🏌🏾\u200d♀️",
+ "woman_golfing_medium-light_skin_tone": "🏌🏼\u200d♀️",
+ "woman_golfing_medium_skin_tone": "🏌🏽\u200d♀️",
+ "woman_guard": "💂\u200d♀️",
+ "woman_guard_dark_skin_tone": "💂🏿\u200d♀️",
+ "woman_guard_light_skin_tone": "💂🏻\u200d♀️",
+ "woman_guard_medium-dark_skin_tone": "💂🏾\u200d♀️",
+ "woman_guard_medium-light_skin_tone": "💂🏼\u200d♀️",
+ "woman_guard_medium_skin_tone": "💂🏽\u200d♀️",
+ "woman_health_worker": "👩\u200d⚕️",
+ "woman_health_worker_dark_skin_tone": "👩🏿\u200d⚕️",
+ "woman_health_worker_light_skin_tone": "👩🏻\u200d⚕️",
+ "woman_health_worker_medium-dark_skin_tone": "👩🏾\u200d⚕️",
+ "woman_health_worker_medium-light_skin_tone": "👩🏼\u200d⚕️",
+ "woman_health_worker_medium_skin_tone": "👩🏽\u200d⚕️",
+ "woman_in_lotus_position": "🧘\u200d♀️",
+ "woman_in_lotus_position_dark_skin_tone": "🧘🏿\u200d♀️",
+ "woman_in_lotus_position_light_skin_tone": "🧘🏻\u200d♀️",
+ "woman_in_lotus_position_medium-dark_skin_tone": "🧘🏾\u200d♀️",
+ "woman_in_lotus_position_medium-light_skin_tone": "🧘🏼\u200d♀️",
+ "woman_in_lotus_position_medium_skin_tone": "🧘🏽\u200d♀️",
+ "woman_in_manual_wheelchair": "👩\u200d🦽",
+ "woman_in_motorized_wheelchair": "👩\u200d🦼",
+ "woman_in_steamy_room": "🧖\u200d♀️",
+ "woman_in_steamy_room_dark_skin_tone": "🧖🏿\u200d♀️",
+ "woman_in_steamy_room_light_skin_tone": "🧖🏻\u200d♀️",
+ "woman_in_steamy_room_medium-dark_skin_tone": "🧖🏾\u200d♀️",
+ "woman_in_steamy_room_medium-light_skin_tone": "🧖🏼\u200d♀️",
+ "woman_in_steamy_room_medium_skin_tone": "🧖🏽\u200d♀️",
+ "woman_judge": "👩\u200d⚖️",
+ "woman_judge_dark_skin_tone": "👩🏿\u200d⚖️",
+ "woman_judge_light_skin_tone": "👩🏻\u200d⚖️",
+ "woman_judge_medium-dark_skin_tone": "👩🏾\u200d⚖️",
+ "woman_judge_medium-light_skin_tone": "👩🏼\u200d⚖️",
+ "woman_judge_medium_skin_tone": "👩🏽\u200d⚖️",
+ "woman_juggling": "🤹\u200d♀️",
+ "woman_juggling_dark_skin_tone": "🤹🏿\u200d♀️",
+ "woman_juggling_light_skin_tone": "🤹🏻\u200d♀️",
+ "woman_juggling_medium-dark_skin_tone": "🤹🏾\u200d♀️",
+ "woman_juggling_medium-light_skin_tone": "🤹🏼\u200d♀️",
+ "woman_juggling_medium_skin_tone": "🤹🏽\u200d♀️",
+ "woman_lifting_weights": "🏋️\u200d♀️",
+ "woman_lifting_weights_dark_skin_tone": "🏋🏿\u200d♀️",
+ "woman_lifting_weights_light_skin_tone": "🏋🏻\u200d♀️",
+ "woman_lifting_weights_medium-dark_skin_tone": "🏋🏾\u200d♀️",
+ "woman_lifting_weights_medium-light_skin_tone": "🏋🏼\u200d♀️",
+ "woman_lifting_weights_medium_skin_tone": "🏋🏽\u200d♀️",
+ "woman_light_skin_tone": "👩🏻",
+ "woman_mage": "🧙\u200d♀️",
+ "woman_mage_dark_skin_tone": "🧙🏿\u200d♀️",
+ "woman_mage_light_skin_tone": "🧙🏻\u200d♀️",
+ "woman_mage_medium-dark_skin_tone": "🧙🏾\u200d♀️",
+ "woman_mage_medium-light_skin_tone": "🧙🏼\u200d♀️",
+ "woman_mage_medium_skin_tone": "🧙🏽\u200d♀️",
+ "woman_mechanic": "👩\u200d🔧",
+ "woman_mechanic_dark_skin_tone": "👩🏿\u200d🔧",
+ "woman_mechanic_light_skin_tone": "👩🏻\u200d🔧",
+ "woman_mechanic_medium-dark_skin_tone": "👩🏾\u200d🔧",
+ "woman_mechanic_medium-light_skin_tone": "👩🏼\u200d🔧",
+ "woman_mechanic_medium_skin_tone": "👩🏽\u200d🔧",
+ "woman_medium-dark_skin_tone": "👩🏾",
+ "woman_medium-light_skin_tone": "👩🏼",
+ "woman_medium_skin_tone": "👩🏽",
+ "woman_mountain_biking": "🚵\u200d♀️",
+ "woman_mountain_biking_dark_skin_tone": "🚵🏿\u200d♀️",
+ "woman_mountain_biking_light_skin_tone": "🚵🏻\u200d♀️",
+ "woman_mountain_biking_medium-dark_skin_tone": "🚵🏾\u200d♀️",
+ "woman_mountain_biking_medium-light_skin_tone": "🚵🏼\u200d♀️",
+ "woman_mountain_biking_medium_skin_tone": "🚵🏽\u200d♀️",
+ "woman_office_worker": "👩\u200d💼",
+ "woman_office_worker_dark_skin_tone": "👩🏿\u200d💼",
+ "woman_office_worker_light_skin_tone": "👩🏻\u200d💼",
+ "woman_office_worker_medium-dark_skin_tone": "👩🏾\u200d💼",
+ "woman_office_worker_medium-light_skin_tone": "👩🏼\u200d💼",
+ "woman_office_worker_medium_skin_tone": "👩🏽\u200d💼",
+ "woman_pilot": "👩\u200d✈️",
+ "woman_pilot_dark_skin_tone": "👩🏿\u200d✈️",
+ "woman_pilot_light_skin_tone": "👩🏻\u200d✈️",
+ "woman_pilot_medium-dark_skin_tone": "👩🏾\u200d✈️",
+ "woman_pilot_medium-light_skin_tone": "👩🏼\u200d✈️",
+ "woman_pilot_medium_skin_tone": "👩🏽\u200d✈️",
+ "woman_playing_handball": "🤾\u200d♀️",
+ "woman_playing_handball_dark_skin_tone": "🤾🏿\u200d♀️",
+ "woman_playing_handball_light_skin_tone": "🤾🏻\u200d♀️",
+ "woman_playing_handball_medium-dark_skin_tone": "🤾🏾\u200d♀️",
+ "woman_playing_handball_medium-light_skin_tone": "🤾🏼\u200d♀️",
+ "woman_playing_handball_medium_skin_tone": "🤾🏽\u200d♀️",
+ "woman_playing_water_polo": "🤽\u200d♀️",
+ "woman_playing_water_polo_dark_skin_tone": "🤽🏿\u200d♀️",
+ "woman_playing_water_polo_light_skin_tone": "🤽🏻\u200d♀️",
+ "woman_playing_water_polo_medium-dark_skin_tone": "🤽🏾\u200d♀️",
+ "woman_playing_water_polo_medium-light_skin_tone": "🤽🏼\u200d♀️",
+ "woman_playing_water_polo_medium_skin_tone": "🤽🏽\u200d♀️",
+ "woman_police_officer": "👮\u200d♀️",
+ "woman_police_officer_dark_skin_tone": "👮🏿\u200d♀️",
+ "woman_police_officer_light_skin_tone": "👮🏻\u200d♀️",
+ "woman_police_officer_medium-dark_skin_tone": "👮🏾\u200d♀️",
+ "woman_police_officer_medium-light_skin_tone": "👮🏼\u200d♀️",
+ "woman_police_officer_medium_skin_tone": "👮🏽\u200d♀️",
+ "woman_pouting": "🙎\u200d♀️",
+ "woman_pouting_dark_skin_tone": "🙎🏿\u200d♀️",
+ "woman_pouting_light_skin_tone": "🙎🏻\u200d♀️",
+ "woman_pouting_medium-dark_skin_tone": "🙎🏾\u200d♀️",
+ "woman_pouting_medium-light_skin_tone": "🙎🏼\u200d♀️",
+ "woman_pouting_medium_skin_tone": "🙎🏽\u200d♀️",
+ "woman_raising_hand": "🙋\u200d♀️",
+ "woman_raising_hand_dark_skin_tone": "🙋🏿\u200d♀️",
+ "woman_raising_hand_light_skin_tone": "🙋🏻\u200d♀️",
+ "woman_raising_hand_medium-dark_skin_tone": "🙋🏾\u200d♀️",
+ "woman_raising_hand_medium-light_skin_tone": "🙋🏼\u200d♀️",
+ "woman_raising_hand_medium_skin_tone": "🙋🏽\u200d♀️",
+ "woman_rowing_boat": "🚣\u200d♀️",
+ "woman_rowing_boat_dark_skin_tone": "🚣🏿\u200d♀️",
+ "woman_rowing_boat_light_skin_tone": "🚣🏻\u200d♀️",
+ "woman_rowing_boat_medium-dark_skin_tone": "🚣🏾\u200d♀️",
+ "woman_rowing_boat_medium-light_skin_tone": "🚣🏼\u200d♀️",
+ "woman_rowing_boat_medium_skin_tone": "🚣🏽\u200d♀️",
+ "woman_running": "🏃\u200d♀️",
+ "woman_running_dark_skin_tone": "🏃🏿\u200d♀️",
+ "woman_running_light_skin_tone": "🏃🏻\u200d♀️",
+ "woman_running_medium-dark_skin_tone": "🏃🏾\u200d♀️",
+ "woman_running_medium-light_skin_tone": "🏃🏼\u200d♀️",
+ "woman_running_medium_skin_tone": "🏃🏽\u200d♀️",
+ "woman_scientist": "👩\u200d🔬",
+ "woman_scientist_dark_skin_tone": "👩🏿\u200d🔬",
+ "woman_scientist_light_skin_tone": "👩🏻\u200d🔬",
+ "woman_scientist_medium-dark_skin_tone": "👩🏾\u200d🔬",
+ "woman_scientist_medium-light_skin_tone": "👩🏼\u200d🔬",
+ "woman_scientist_medium_skin_tone": "👩🏽\u200d🔬",
+ "woman_shrugging": "🤷\u200d♀️",
+ "woman_shrugging_dark_skin_tone": "🤷🏿\u200d♀️",
+ "woman_shrugging_light_skin_tone": "🤷🏻\u200d♀️",
+ "woman_shrugging_medium-dark_skin_tone": "🤷🏾\u200d♀️",
+ "woman_shrugging_medium-light_skin_tone": "🤷🏼\u200d♀️",
+ "woman_shrugging_medium_skin_tone": "🤷🏽\u200d♀️",
+ "woman_singer": "👩\u200d🎤",
+ "woman_singer_dark_skin_tone": "👩🏿\u200d🎤",
+ "woman_singer_light_skin_tone": "👩🏻\u200d🎤",
+ "woman_singer_medium-dark_skin_tone": "👩🏾\u200d🎤",
+ "woman_singer_medium-light_skin_tone": "👩🏼\u200d🎤",
+ "woman_singer_medium_skin_tone": "👩🏽\u200d🎤",
+ "woman_student": "👩\u200d🎓",
+ "woman_student_dark_skin_tone": "👩🏿\u200d🎓",
+ "woman_student_light_skin_tone": "👩🏻\u200d🎓",
+ "woman_student_medium-dark_skin_tone": "👩🏾\u200d🎓",
+ "woman_student_medium-light_skin_tone": "👩🏼\u200d🎓",
+ "woman_student_medium_skin_tone": "👩🏽\u200d🎓",
+ "woman_surfing": "🏄\u200d♀️",
+ "woman_surfing_dark_skin_tone": "🏄🏿\u200d♀️",
+ "woman_surfing_light_skin_tone": "🏄🏻\u200d♀️",
+ "woman_surfing_medium-dark_skin_tone": "🏄🏾\u200d♀️",
+ "woman_surfing_medium-light_skin_tone": "🏄🏼\u200d♀️",
+ "woman_surfing_medium_skin_tone": "🏄🏽\u200d♀️",
+ "woman_swimming": "🏊\u200d♀️",
+ "woman_swimming_dark_skin_tone": "🏊🏿\u200d♀️",
+ "woman_swimming_light_skin_tone": "🏊🏻\u200d♀️",
+ "woman_swimming_medium-dark_skin_tone": "🏊🏾\u200d♀️",
+ "woman_swimming_medium-light_skin_tone": "🏊🏼\u200d♀️",
+ "woman_swimming_medium_skin_tone": "🏊🏽\u200d♀️",
+ "woman_teacher": "👩\u200d🏫",
+ "woman_teacher_dark_skin_tone": "👩🏿\u200d🏫",
+ "woman_teacher_light_skin_tone": "👩🏻\u200d🏫",
+ "woman_teacher_medium-dark_skin_tone": "👩🏾\u200d🏫",
+ "woman_teacher_medium-light_skin_tone": "👩🏼\u200d🏫",
+ "woman_teacher_medium_skin_tone": "👩🏽\u200d🏫",
+ "woman_technologist": "👩\u200d💻",
+ "woman_technologist_dark_skin_tone": "👩🏿\u200d💻",
+ "woman_technologist_light_skin_tone": "👩🏻\u200d💻",
+ "woman_technologist_medium-dark_skin_tone": "👩🏾\u200d💻",
+ "woman_technologist_medium-light_skin_tone": "👩🏼\u200d💻",
+ "woman_technologist_medium_skin_tone": "👩🏽\u200d💻",
+ "woman_tipping_hand": "💁\u200d♀️",
+ "woman_tipping_hand_dark_skin_tone": "💁🏿\u200d♀️",
+ "woman_tipping_hand_light_skin_tone": "💁🏻\u200d♀️",
+ "woman_tipping_hand_medium-dark_skin_tone": "💁🏾\u200d♀️",
+ "woman_tipping_hand_medium-light_skin_tone": "💁🏼\u200d♀️",
+ "woman_tipping_hand_medium_skin_tone": "💁🏽\u200d♀️",
+ "woman_vampire": "🧛\u200d♀️",
+ "woman_vampire_dark_skin_tone": "🧛🏿\u200d♀️",
+ "woman_vampire_light_skin_tone": "🧛🏻\u200d♀️",
+ "woman_vampire_medium-dark_skin_tone": "🧛🏾\u200d♀️",
+ "woman_vampire_medium-light_skin_tone": "🧛🏼\u200d♀️",
+ "woman_vampire_medium_skin_tone": "🧛🏽\u200d♀️",
+ "woman_walking": "🚶\u200d♀️",
+ "woman_walking_dark_skin_tone": "🚶🏿\u200d♀️",
+ "woman_walking_light_skin_tone": "🚶🏻\u200d♀️",
+ "woman_walking_medium-dark_skin_tone": "🚶🏾\u200d♀️",
+ "woman_walking_medium-light_skin_tone": "🚶🏼\u200d♀️",
+ "woman_walking_medium_skin_tone": "🚶🏽\u200d♀️",
+ "woman_wearing_turban": "👳\u200d♀️",
+ "woman_wearing_turban_dark_skin_tone": "👳🏿\u200d♀️",
+ "woman_wearing_turban_light_skin_tone": "👳🏻\u200d♀️",
+ "woman_wearing_turban_medium-dark_skin_tone": "👳🏾\u200d♀️",
+ "woman_wearing_turban_medium-light_skin_tone": "👳🏼\u200d♀️",
+ "woman_wearing_turban_medium_skin_tone": "👳🏽\u200d♀️",
+ "woman_with_headscarf": "🧕",
+ "woman_with_headscarf_dark_skin_tone": "🧕🏿",
+ "woman_with_headscarf_light_skin_tone": "🧕🏻",
+ "woman_with_headscarf_medium-dark_skin_tone": "🧕🏾",
+ "woman_with_headscarf_medium-light_skin_tone": "🧕🏼",
+ "woman_with_headscarf_medium_skin_tone": "🧕🏽",
+ "woman_with_probing_cane": "👩\u200d🦯",
+ "woman_zombie": "🧟\u200d♀️",
+ "woman’s_boot": "👢",
+ "woman’s_clothes": "👚",
+ "woman’s_hat": "👒",
+ "woman’s_sandal": "👡",
+ "women_with_bunny_ears": "👯\u200d♀️",
+ "women_wrestling": "🤼\u200d♀️",
+ "women’s_room": "🚺",
+ "woozy_face": "🥴",
+ "world_map": "🗺",
+ "worried_face": "😟",
+ "wrapped_gift": "🎁",
+ "wrench": "🔧",
+ "writing_hand": "✍",
+ "writing_hand_dark_skin_tone": "✍🏿",
+ "writing_hand_light_skin_tone": "✍🏻",
+ "writing_hand_medium-dark_skin_tone": "✍🏾",
+ "writing_hand_medium-light_skin_tone": "✍🏼",
+ "writing_hand_medium_skin_tone": "✍🏽",
+ "yarn": "🧶",
+ "yawning_face": "🥱",
+ "yellow_circle": "🟡",
+ "yellow_heart": "💛",
+ "yellow_square": "🟨",
+ "yen_banknote": "💴",
+ "yo-yo": "🪀",
+ "yin_yang": "☯",
+ "zany_face": "🤪",
+ "zebra": "🦓",
+ "zipper-mouth_face": "🤐",
+ "zombie": "🧟",
+ "zzz": "💤",
+ "åland_islands": "🇦🇽",
+ "keycap_asterisk": "*⃣",
+ "keycap_digit_eight": "8⃣",
+ "keycap_digit_five": "5⃣",
+ "keycap_digit_four": "4⃣",
+ "keycap_digit_nine": "9⃣",
+ "keycap_digit_one": "1⃣",
+ "keycap_digit_seven": "7⃣",
+ "keycap_digit_six": "6⃣",
+ "keycap_digit_three": "3⃣",
+ "keycap_digit_two": "2⃣",
+ "keycap_digit_zero": "0⃣",
+ "keycap_number_sign": "#⃣",
+ "light_skin_tone": "🏻",
+ "medium_light_skin_tone": "🏼",
+ "medium_skin_tone": "🏽",
+ "medium_dark_skin_tone": "🏾",
+ "dark_skin_tone": "🏿",
+ "regional_indicator_symbol_letter_a": "🇦",
+ "regional_indicator_symbol_letter_b": "🇧",
+ "regional_indicator_symbol_letter_c": "🇨",
+ "regional_indicator_symbol_letter_d": "🇩",
+ "regional_indicator_symbol_letter_e": "🇪",
+ "regional_indicator_symbol_letter_f": "🇫",
+ "regional_indicator_symbol_letter_g": "🇬",
+ "regional_indicator_symbol_letter_h": "🇭",
+ "regional_indicator_symbol_letter_i": "🇮",
+ "regional_indicator_symbol_letter_j": "🇯",
+ "regional_indicator_symbol_letter_k": "🇰",
+ "regional_indicator_symbol_letter_l": "🇱",
+ "regional_indicator_symbol_letter_m": "🇲",
+ "regional_indicator_symbol_letter_n": "🇳",
+ "regional_indicator_symbol_letter_o": "🇴",
+ "regional_indicator_symbol_letter_p": "🇵",
+ "regional_indicator_symbol_letter_q": "🇶",
+ "regional_indicator_symbol_letter_r": "🇷",
+ "regional_indicator_symbol_letter_s": "🇸",
+ "regional_indicator_symbol_letter_t": "🇹",
+ "regional_indicator_symbol_letter_u": "🇺",
+ "regional_indicator_symbol_letter_v": "🇻",
+ "regional_indicator_symbol_letter_w": "🇼",
+ "regional_indicator_symbol_letter_x": "🇽",
+ "regional_indicator_symbol_letter_y": "🇾",
+ "regional_indicator_symbol_letter_z": "🇿",
+ "airplane_arriving": "🛬",
+ "space_invader": "👾",
+ "football": "🏈",
+ "anger": "💢",
+ "angry": "😠",
+ "anguished": "😧",
+ "signal_strength": "📶",
+ "arrows_counterclockwise": "🔄",
+ "arrow_heading_down": "⤵",
+ "arrow_heading_up": "⤴",
+ "art": "🎨",
+ "astonished": "😲",
+ "athletic_shoe": "👟",
+ "atm": "🏧",
+ "car": "🚗",
+ "red_car": "🚗",
+ "angel": "👼",
+ "back": "🔙",
+ "badminton_racquet_and_shuttlecock": "🏸",
+ "dollar": "💵",
+ "euro": "💶",
+ "pound": "💷",
+ "yen": "💴",
+ "barber": "💈",
+ "bath": "🛀",
+ "bear": "🐻",
+ "heartbeat": "💓",
+ "beer": "🍺",
+ "no_bell": "🔕",
+ "bento": "🍱",
+ "bike": "🚲",
+ "bicyclist": "🚴",
+ "8ball": "🎱",
+ "biohazard_sign": "☣",
+ "birthday": "🎂",
+ "black_circle_for_record": "⏺",
+ "clubs": "♣",
+ "diamonds": "♦",
+ "arrow_double_down": "⏬",
+ "hearts": "♥",
+ "rewind": "⏪",
+ "black_left__pointing_double_triangle_with_vertical_bar": "⏮",
+ "arrow_backward": "◀",
+ "black_medium_small_square": "◾",
+ "question": "❓",
+ "fast_forward": "⏩",
+ "black_right__pointing_double_triangle_with_vertical_bar": "⏭",
+ "arrow_forward": "▶",
+ "black_right__pointing_triangle_with_double_vertical_bar": "⏯",
+ "arrow_right": "➡",
+ "spades": "♠",
+ "black_square_for_stop": "⏹",
+ "sunny": "☀",
+ "phone": "☎",
+ "recycle": "♻",
+ "arrow_double_up": "⏫",
+ "busstop": "🚏",
+ "date": "📅",
+ "flags": "🎏",
+ "cat2": "🐈",
+ "joy_cat": "😹",
+ "smirk_cat": "😼",
+ "chart_with_downwards_trend": "📉",
+ "chart_with_upwards_trend": "📈",
+ "chart": "💹",
+ "mega": "📣",
+ "checkered_flag": "🏁",
+ "accept": "🉑",
+ "ideograph_advantage": "🉐",
+ "congratulations": "㊗",
+ "secret": "㊙",
+ "m": "Ⓜ",
+ "city_sunset": "🌆",
+ "clapper": "🎬",
+ "clap": "👏",
+ "beers": "🍻",
+ "clock830": "🕣",
+ "clock8": "🕗",
+ "clock1130": "🕦",
+ "clock11": "🕚",
+ "clock530": "🕠",
+ "clock5": "🕔",
+ "clock430": "🕟",
+ "clock4": "🕓",
+ "clock930": "🕤",
+ "clock9": "🕘",
+ "clock130": "🕜",
+ "clock1": "🕐",
+ "clock730": "🕢",
+ "clock7": "🕖",
+ "clock630": "🕡",
+ "clock6": "🕕",
+ "clock1030": "🕥",
+ "clock10": "🕙",
+ "clock330": "🕞",
+ "clock3": "🕒",
+ "clock1230": "🕧",
+ "clock12": "🕛",
+ "clock230": "🕝",
+ "clock2": "🕑",
+ "arrows_clockwise": "🔃",
+ "repeat": "🔁",
+ "repeat_one": "🔂",
+ "closed_lock_with_key": "🔐",
+ "mailbox_closed": "📪",
+ "mailbox": "📫",
+ "cloud_with_tornado": "🌪",
+ "cocktail": "🍸",
+ "boom": "💥",
+ "compression": "🗜",
+ "confounded": "😖",
+ "confused": "😕",
+ "rice": "🍚",
+ "cow2": "🐄",
+ "cricket_bat_and_ball": "🏏",
+ "x": "❌",
+ "cry": "😢",
+ "curry": "🍛",
+ "dagger_knife": "🗡",
+ "dancer": "💃",
+ "dark_sunglasses": "🕶",
+ "dash": "💨",
+ "truck": "🚚",
+ "derelict_house_building": "🏚",
+ "diamond_shape_with_a_dot_inside": "💠",
+ "dart": "🎯",
+ "disappointed_relieved": "😥",
+ "disappointed": "😞",
+ "do_not_litter": "🚯",
+ "dog2": "🐕",
+ "flipper": "🐬",
+ "loop": "➿",
+ "bangbang": "‼",
+ "double_vertical_bar": "⏸",
+ "dove_of_peace": "🕊",
+ "small_red_triangle_down": "🔻",
+ "arrow_down_small": "🔽",
+ "arrow_down": "⬇",
+ "dromedary_camel": "🐪",
+ "e__mail": "📧",
+ "corn": "🌽",
+ "ear_of_rice": "🌾",
+ "earth_americas": "🌎",
+ "earth_asia": "🌏",
+ "earth_africa": "🌍",
+ "eight_pointed_black_star": "✴",
+ "eight_spoked_asterisk": "✳",
+ "eject_symbol": "⏏",
+ "bulb": "💡",
+ "emoji_modifier_fitzpatrick_type__1__2": "🏻",
+ "emoji_modifier_fitzpatrick_type__3": "🏼",
+ "emoji_modifier_fitzpatrick_type__4": "🏽",
+ "emoji_modifier_fitzpatrick_type__5": "🏾",
+ "emoji_modifier_fitzpatrick_type__6": "🏿",
+ "end": "🔚",
+ "email": "✉",
+ "european_castle": "🏰",
+ "european_post_office": "🏤",
+ "interrobang": "⁉",
+ "expressionless": "😑",
+ "eyeglasses": "👓",
+ "massage": "💆",
+ "yum": "😋",
+ "scream": "😱",
+ "kissing_heart": "😘",
+ "sweat": "😓",
+ "face_with_head__bandage": "🤕",
+ "triumph": "😤",
+ "mask": "😷",
+ "no_good": "🙅",
+ "ok_woman": "🙆",
+ "open_mouth": "😮",
+ "cold_sweat": "😰",
+ "stuck_out_tongue": "😛",
+ "stuck_out_tongue_closed_eyes": "😝",
+ "stuck_out_tongue_winking_eye": "😜",
+ "joy": "😂",
+ "no_mouth": "😶",
+ "santa": "🎅",
+ "fax": "📠",
+ "fearful": "😨",
+ "field_hockey_stick_and_ball": "🏑",
+ "first_quarter_moon_with_face": "🌛",
+ "fish_cake": "🍥",
+ "fishing_pole_and_fish": "🎣",
+ "facepunch": "👊",
+ "punch": "👊",
+ "flag_for_afghanistan": "🇦🇫",
+ "flag_for_albania": "🇦🇱",
+ "flag_for_algeria": "🇩🇿",
+ "flag_for_american_samoa": "🇦🇸",
+ "flag_for_andorra": "🇦🇩",
+ "flag_for_angola": "🇦🇴",
+ "flag_for_anguilla": "🇦🇮",
+ "flag_for_antarctica": "🇦🇶",
+ "flag_for_antigua_&_barbuda": "🇦🇬",
+ "flag_for_argentina": "🇦🇷",
+ "flag_for_armenia": "🇦🇲",
+ "flag_for_aruba": "🇦🇼",
+ "flag_for_ascension_island": "🇦🇨",
+ "flag_for_australia": "🇦🇺",
+ "flag_for_austria": "🇦🇹",
+ "flag_for_azerbaijan": "🇦🇿",
+ "flag_for_bahamas": "🇧🇸",
+ "flag_for_bahrain": "🇧🇭",
+ "flag_for_bangladesh": "🇧🇩",
+ "flag_for_barbados": "🇧🇧",
+ "flag_for_belarus": "🇧🇾",
+ "flag_for_belgium": "🇧🇪",
+ "flag_for_belize": "🇧🇿",
+ "flag_for_benin": "🇧🇯",
+ "flag_for_bermuda": "🇧🇲",
+ "flag_for_bhutan": "🇧🇹",
+ "flag_for_bolivia": "🇧🇴",
+ "flag_for_bosnia_&_herzegovina": "🇧🇦",
+ "flag_for_botswana": "🇧🇼",
+ "flag_for_bouvet_island": "🇧🇻",
+ "flag_for_brazil": "🇧🇷",
+ "flag_for_british_indian_ocean_territory": "🇮🇴",
+ "flag_for_british_virgin_islands": "🇻🇬",
+ "flag_for_brunei": "🇧🇳",
+ "flag_for_bulgaria": "🇧🇬",
+ "flag_for_burkina_faso": "🇧🇫",
+ "flag_for_burundi": "🇧🇮",
+ "flag_for_cambodia": "🇰🇭",
+ "flag_for_cameroon": "🇨🇲",
+ "flag_for_canada": "🇨🇦",
+ "flag_for_canary_islands": "🇮🇨",
+ "flag_for_cape_verde": "🇨🇻",
+ "flag_for_caribbean_netherlands": "🇧🇶",
+ "flag_for_cayman_islands": "🇰🇾",
+ "flag_for_central_african_republic": "🇨🇫",
+ "flag_for_ceuta_&_melilla": "🇪🇦",
+ "flag_for_chad": "🇹🇩",
+ "flag_for_chile": "🇨🇱",
+ "flag_for_china": "🇨🇳",
+ "flag_for_christmas_island": "🇨🇽",
+ "flag_for_clipperton_island": "🇨🇵",
+ "flag_for_cocos__islands": "🇨🇨",
+ "flag_for_colombia": "🇨🇴",
+ "flag_for_comoros": "🇰🇲",
+ "flag_for_congo____brazzaville": "🇨🇬",
+ "flag_for_congo____kinshasa": "🇨🇩",
+ "flag_for_cook_islands": "🇨🇰",
+ "flag_for_costa_rica": "🇨🇷",
+ "flag_for_croatia": "🇭🇷",
+ "flag_for_cuba": "🇨🇺",
+ "flag_for_curaçao": "🇨🇼",
+ "flag_for_cyprus": "🇨🇾",
+ "flag_for_czech_republic": "🇨🇿",
+ "flag_for_côte_d’ivoire": "🇨🇮",
+ "flag_for_denmark": "🇩🇰",
+ "flag_for_diego_garcia": "🇩🇬",
+ "flag_for_djibouti": "🇩🇯",
+ "flag_for_dominica": "🇩🇲",
+ "flag_for_dominican_republic": "🇩🇴",
+ "flag_for_ecuador": "🇪🇨",
+ "flag_for_egypt": "🇪🇬",
+ "flag_for_el_salvador": "🇸🇻",
+ "flag_for_equatorial_guinea": "🇬🇶",
+ "flag_for_eritrea": "🇪🇷",
+ "flag_for_estonia": "🇪🇪",
+ "flag_for_ethiopia": "🇪🇹",
+ "flag_for_european_union": "🇪🇺",
+ "flag_for_falkland_islands": "🇫🇰",
+ "flag_for_faroe_islands": "🇫🇴",
+ "flag_for_fiji": "🇫🇯",
+ "flag_for_finland": "🇫🇮",
+ "flag_for_france": "🇫🇷",
+ "flag_for_french_guiana": "🇬🇫",
+ "flag_for_french_polynesia": "🇵🇫",
+ "flag_for_french_southern_territories": "🇹🇫",
+ "flag_for_gabon": "🇬🇦",
+ "flag_for_gambia": "🇬🇲",
+ "flag_for_georgia": "🇬🇪",
+ "flag_for_germany": "🇩🇪",
+ "flag_for_ghana": "🇬🇭",
+ "flag_for_gibraltar": "🇬🇮",
+ "flag_for_greece": "🇬🇷",
+ "flag_for_greenland": "🇬🇱",
+ "flag_for_grenada": "🇬🇩",
+ "flag_for_guadeloupe": "🇬🇵",
+ "flag_for_guam": "🇬🇺",
+ "flag_for_guatemala": "🇬🇹",
+ "flag_for_guernsey": "🇬🇬",
+ "flag_for_guinea": "🇬🇳",
+ "flag_for_guinea__bissau": "🇬🇼",
+ "flag_for_guyana": "🇬🇾",
+ "flag_for_haiti": "🇭🇹",
+ "flag_for_heard_&_mcdonald_islands": "🇭🇲",
+ "flag_for_honduras": "🇭🇳",
+ "flag_for_hong_kong": "🇭🇰",
+ "flag_for_hungary": "🇭🇺",
+ "flag_for_iceland": "🇮🇸",
+ "flag_for_india": "🇮🇳",
+ "flag_for_indonesia": "🇮🇩",
+ "flag_for_iran": "🇮🇷",
+ "flag_for_iraq": "🇮🇶",
+ "flag_for_ireland": "🇮🇪",
+ "flag_for_isle_of_man": "🇮🇲",
+ "flag_for_israel": "🇮🇱",
+ "flag_for_italy": "🇮🇹",
+ "flag_for_jamaica": "🇯🇲",
+ "flag_for_japan": "🇯🇵",
+ "flag_for_jersey": "🇯🇪",
+ "flag_for_jordan": "🇯🇴",
+ "flag_for_kazakhstan": "🇰🇿",
+ "flag_for_kenya": "🇰🇪",
+ "flag_for_kiribati": "🇰🇮",
+ "flag_for_kosovo": "🇽🇰",
+ "flag_for_kuwait": "🇰🇼",
+ "flag_for_kyrgyzstan": "🇰🇬",
+ "flag_for_laos": "🇱🇦",
+ "flag_for_latvia": "🇱🇻",
+ "flag_for_lebanon": "🇱🇧",
+ "flag_for_lesotho": "🇱🇸",
+ "flag_for_liberia": "🇱🇷",
+ "flag_for_libya": "🇱🇾",
+ "flag_for_liechtenstein": "🇱🇮",
+ "flag_for_lithuania": "🇱🇹",
+ "flag_for_luxembourg": "🇱🇺",
+ "flag_for_macau": "🇲🇴",
+ "flag_for_macedonia": "🇲🇰",
+ "flag_for_madagascar": "🇲🇬",
+ "flag_for_malawi": "🇲🇼",
+ "flag_for_malaysia": "🇲🇾",
+ "flag_for_maldives": "🇲🇻",
+ "flag_for_mali": "🇲🇱",
+ "flag_for_malta": "🇲🇹",
+ "flag_for_marshall_islands": "🇲🇭",
+ "flag_for_martinique": "🇲🇶",
+ "flag_for_mauritania": "🇲🇷",
+ "flag_for_mauritius": "🇲🇺",
+ "flag_for_mayotte": "🇾🇹",
+ "flag_for_mexico": "🇲🇽",
+ "flag_for_micronesia": "🇫🇲",
+ "flag_for_moldova": "🇲🇩",
+ "flag_for_monaco": "🇲🇨",
+ "flag_for_mongolia": "🇲🇳",
+ "flag_for_montenegro": "🇲🇪",
+ "flag_for_montserrat": "🇲🇸",
+ "flag_for_morocco": "🇲🇦",
+ "flag_for_mozambique": "🇲🇿",
+ "flag_for_myanmar": "🇲🇲",
+ "flag_for_namibia": "🇳🇦",
+ "flag_for_nauru": "🇳🇷",
+ "flag_for_nepal": "🇳🇵",
+ "flag_for_netherlands": "🇳🇱",
+ "flag_for_new_caledonia": "🇳🇨",
+ "flag_for_new_zealand": "🇳🇿",
+ "flag_for_nicaragua": "🇳🇮",
+ "flag_for_niger": "🇳🇪",
+ "flag_for_nigeria": "🇳🇬",
+ "flag_for_niue": "🇳🇺",
+ "flag_for_norfolk_island": "🇳🇫",
+ "flag_for_north_korea": "🇰🇵",
+ "flag_for_northern_mariana_islands": "🇲🇵",
+ "flag_for_norway": "🇳🇴",
+ "flag_for_oman": "🇴🇲",
+ "flag_for_pakistan": "🇵🇰",
+ "flag_for_palau": "🇵🇼",
+ "flag_for_palestinian_territories": "🇵🇸",
+ "flag_for_panama": "🇵🇦",
+ "flag_for_papua_new_guinea": "🇵🇬",
+ "flag_for_paraguay": "🇵🇾",
+ "flag_for_peru": "🇵🇪",
+ "flag_for_philippines": "🇵🇭",
+ "flag_for_pitcairn_islands": "🇵🇳",
+ "flag_for_poland": "🇵🇱",
+ "flag_for_portugal": "🇵🇹",
+ "flag_for_puerto_rico": "🇵🇷",
+ "flag_for_qatar": "🇶🇦",
+ "flag_for_romania": "🇷🇴",
+ "flag_for_russia": "🇷🇺",
+ "flag_for_rwanda": "🇷🇼",
+ "flag_for_réunion": "🇷🇪",
+ "flag_for_samoa": "🇼🇸",
+ "flag_for_san_marino": "🇸🇲",
+ "flag_for_saudi_arabia": "🇸🇦",
+ "flag_for_senegal": "🇸🇳",
+ "flag_for_serbia": "🇷🇸",
+ "flag_for_seychelles": "🇸🇨",
+ "flag_for_sierra_leone": "🇸🇱",
+ "flag_for_singapore": "🇸🇬",
+ "flag_for_sint_maarten": "🇸🇽",
+ "flag_for_slovakia": "🇸🇰",
+ "flag_for_slovenia": "🇸🇮",
+ "flag_for_solomon_islands": "🇸🇧",
+ "flag_for_somalia": "🇸🇴",
+ "flag_for_south_africa": "🇿🇦",
+ "flag_for_south_georgia_&_south_sandwich_islands": "🇬🇸",
+ "flag_for_south_korea": "🇰🇷",
+ "flag_for_south_sudan": "🇸🇸",
+ "flag_for_spain": "🇪🇸",
+ "flag_for_sri_lanka": "🇱🇰",
+ "flag_for_st._barthélemy": "🇧🇱",
+ "flag_for_st._helena": "🇸🇭",
+ "flag_for_st._kitts_&_nevis": "🇰🇳",
+ "flag_for_st._lucia": "🇱🇨",
+ "flag_for_st._martin": "🇲🇫",
+ "flag_for_st._pierre_&_miquelon": "🇵🇲",
+ "flag_for_st._vincent_&_grenadines": "🇻🇨",
+ "flag_for_sudan": "🇸🇩",
+ "flag_for_suriname": "🇸🇷",
+ "flag_for_svalbard_&_jan_mayen": "🇸🇯",
+ "flag_for_swaziland": "🇸🇿",
+ "flag_for_sweden": "🇸🇪",
+ "flag_for_switzerland": "🇨🇭",
+ "flag_for_syria": "🇸🇾",
+ "flag_for_são_tomé_&_príncipe": "🇸🇹",
+ "flag_for_taiwan": "🇹🇼",
+ "flag_for_tajikistan": "🇹🇯",
+ "flag_for_tanzania": "🇹🇿",
+ "flag_for_thailand": "🇹🇭",
+ "flag_for_timor__leste": "🇹🇱",
+ "flag_for_togo": "🇹🇬",
+ "flag_for_tokelau": "🇹🇰",
+ "flag_for_tonga": "🇹🇴",
+ "flag_for_trinidad_&_tobago": "🇹🇹",
+ "flag_for_tristan_da_cunha": "🇹🇦",
+ "flag_for_tunisia": "🇹🇳",
+ "flag_for_turkey": "🇹🇷",
+ "flag_for_turkmenistan": "🇹🇲",
+ "flag_for_turks_&_caicos_islands": "🇹🇨",
+ "flag_for_tuvalu": "🇹🇻",
+ "flag_for_u.s._outlying_islands": "🇺🇲",
+ "flag_for_u.s._virgin_islands": "🇻🇮",
+ "flag_for_uganda": "🇺🇬",
+ "flag_for_ukraine": "🇺🇦",
+ "flag_for_united_arab_emirates": "🇦🇪",
+ "flag_for_united_kingdom": "🇬🇧",
+ "flag_for_united_states": "🇺🇸",
+ "flag_for_uruguay": "🇺🇾",
+ "flag_for_uzbekistan": "🇺🇿",
+ "flag_for_vanuatu": "🇻🇺",
+ "flag_for_vatican_city": "🇻🇦",
+ "flag_for_venezuela": "🇻🇪",
+ "flag_for_vietnam": "🇻🇳",
+ "flag_for_wallis_&_futuna": "🇼🇫",
+ "flag_for_western_sahara": "🇪🇭",
+ "flag_for_yemen": "🇾🇪",
+ "flag_for_zambia": "🇿🇲",
+ "flag_for_zimbabwe": "🇿🇼",
+ "flag_for_åland_islands": "🇦🇽",
+ "golf": "⛳",
+ "fleur__de__lis": "⚜",
+ "muscle": "💪",
+ "flushed": "😳",
+ "frame_with_picture": "🖼",
+ "fries": "🍟",
+ "frog": "🐸",
+ "hatched_chick": "🐥",
+ "frowning": "😦",
+ "fuelpump": "⛽",
+ "full_moon_with_face": "🌝",
+ "gem": "💎",
+ "star2": "🌟",
+ "golfer": "🏌",
+ "mortar_board": "🎓",
+ "grimacing": "😬",
+ "smile_cat": "😸",
+ "grinning": "😀",
+ "grin": "😁",
+ "heartpulse": "💗",
+ "guardsman": "💂",
+ "haircut": "💇",
+ "hamster": "🐹",
+ "raising_hand": "🙋",
+ "headphones": "🎧",
+ "hear_no_evil": "🙉",
+ "cupid": "💘",
+ "gift_heart": "💝",
+ "heart": "❤",
+ "exclamation": "❗",
+ "heavy_exclamation_mark": "❗",
+ "heavy_heart_exclamation_mark_ornament": "❣",
+ "o": "⭕",
+ "helm_symbol": "⎈",
+ "helmet_with_white_cross": "⛑",
+ "high_heel": "👠",
+ "bullettrain_side": "🚄",
+ "bullettrain_front": "🚅",
+ "high_brightness": "🔆",
+ "zap": "⚡",
+ "hocho": "🔪",
+ "knife": "🔪",
+ "bee": "🐝",
+ "traffic_light": "🚥",
+ "racehorse": "🐎",
+ "coffee": "☕",
+ "hotsprings": "♨",
+ "hourglass": "⌛",
+ "hourglass_flowing_sand": "⏳",
+ "house_buildings": "🏘",
+ "100": "💯",
+ "hushed": "😯",
+ "ice_hockey_stick_and_puck": "🏒",
+ "imp": "👿",
+ "information_desk_person": "💁",
+ "information_source": "ℹ",
+ "capital_abcd": "🔠",
+ "abc": "🔤",
+ "abcd": "🔡",
+ "1234": "🔢",
+ "symbols": "🔣",
+ "izakaya_lantern": "🏮",
+ "lantern": "🏮",
+ "jack_o_lantern": "🎃",
+ "dolls": "🎎",
+ "japanese_goblin": "👺",
+ "japanese_ogre": "👹",
+ "beginner": "🔰",
+ "zero": "0️⃣",
+ "one": "1️⃣",
+ "ten": "🔟",
+ "two": "2️⃣",
+ "three": "3️⃣",
+ "four": "4️⃣",
+ "five": "5️⃣",
+ "six": "6️⃣",
+ "seven": "7️⃣",
+ "eight": "8️⃣",
+ "nine": "9️⃣",
+ "couplekiss": "💏",
+ "kissing_cat": "😽",
+ "kissing": "😗",
+ "kissing_closed_eyes": "😚",
+ "kissing_smiling_eyes": "😙",
+ "beetle": "🐞",
+ "large_blue_circle": "🔵",
+ "last_quarter_moon_with_face": "🌜",
+ "leaves": "🍃",
+ "mag": "🔍",
+ "left_right_arrow": "↔",
+ "leftwards_arrow_with_hook": "↩",
+ "arrow_left": "⬅",
+ "lock": "🔒",
+ "lock_with_ink_pen": "🔏",
+ "sob": "😭",
+ "low_brightness": "🔅",
+ "lower_left_ballpoint_pen": "🖊",
+ "lower_left_crayon": "🖍",
+ "lower_left_fountain_pen": "🖋",
+ "lower_left_paintbrush": "🖌",
+ "mahjong": "🀄",
+ "couple": "👫",
+ "man_in_business_suit_levitating": "🕴",
+ "man_with_gua_pi_mao": "👲",
+ "man_with_turban": "👳",
+ "mans_shoe": "👞",
+ "shoe": "👞",
+ "menorah_with_nine_branches": "🕎",
+ "mens": "🚹",
+ "minidisc": "💽",
+ "iphone": "📱",
+ "calling": "📲",
+ "money__mouth_face": "🤑",
+ "moneybag": "💰",
+ "rice_scene": "🎑",
+ "mountain_bicyclist": "🚵",
+ "mouse2": "🐁",
+ "lips": "👄",
+ "moyai": "🗿",
+ "notes": "🎶",
+ "nail_care": "💅",
+ "ab": "🆎",
+ "negative_squared_cross_mark": "❎",
+ "a": "🅰",
+ "b": "🅱",
+ "o2": "🅾",
+ "parking": "🅿",
+ "new_moon_with_face": "🌚",
+ "no_entry_sign": "🚫",
+ "underage": "🔞",
+ "non__potable_water": "🚱",
+ "arrow_upper_right": "↗",
+ "arrow_upper_left": "↖",
+ "office": "🏢",
+ "older_man": "👴",
+ "older_woman": "👵",
+ "om_symbol": "🕉",
+ "on": "🔛",
+ "book": "📖",
+ "unlock": "🔓",
+ "mailbox_with_no_mail": "📭",
+ "mailbox_with_mail": "📬",
+ "cd": "💿",
+ "tada": "🎉",
+ "feet": "🐾",
+ "walking": "🚶",
+ "pencil2": "✏",
+ "pensive": "😔",
+ "persevere": "😣",
+ "bow": "🙇",
+ "raised_hands": "🙌",
+ "person_with_ball": "⛹",
+ "person_with_blond_hair": "👱",
+ "pray": "🙏",
+ "person_with_pouting_face": "🙎",
+ "computer": "💻",
+ "pig2": "🐖",
+ "hankey": "💩",
+ "poop": "💩",
+ "shit": "💩",
+ "bamboo": "🎍",
+ "gun": "🔫",
+ "black_joker": "🃏",
+ "rotating_light": "🚨",
+ "cop": "👮",
+ "stew": "🍲",
+ "pouch": "👝",
+ "pouting_cat": "😾",
+ "rage": "😡",
+ "put_litter_in_its_place": "🚮",
+ "rabbit2": "🐇",
+ "racing_motorcycle": "🏍",
+ "radioactive_sign": "☢",
+ "fist": "✊",
+ "hand": "✋",
+ "raised_hand_with_fingers_splayed": "🖐",
+ "raised_hand_with_part_between_middle_and_ring_fingers": "🖖",
+ "blue_car": "🚙",
+ "apple": "🍎",
+ "relieved": "😌",
+ "reversed_hand_with_middle_finger_extended": "🖕",
+ "mag_right": "🔎",
+ "arrow_right_hook": "↪",
+ "sweet_potato": "🍠",
+ "robot": "🤖",
+ "rolled__up_newspaper": "🗞",
+ "rowboat": "🚣",
+ "runner": "🏃",
+ "running": "🏃",
+ "running_shirt_with_sash": "🎽",
+ "boat": "⛵",
+ "scales": "⚖",
+ "school_satchel": "🎒",
+ "scorpius": "♏",
+ "see_no_evil": "🙈",
+ "sheep": "🐑",
+ "stars": "🌠",
+ "cake": "🍰",
+ "six_pointed_star": "🔯",
+ "ski": "🎿",
+ "sleeping_accommodation": "🛌",
+ "sleeping": "😴",
+ "sleepy": "😪",
+ "sleuth_or_spy": "🕵",
+ "heart_eyes_cat": "😻",
+ "smiley_cat": "😺",
+ "innocent": "😇",
+ "heart_eyes": "😍",
+ "smiling_imp": "😈",
+ "smiley": "😃",
+ "sweat_smile": "😅",
+ "smile": "😄",
+ "laughing": "😆",
+ "satisfied": "😆",
+ "blush": "😊",
+ "smirk": "😏",
+ "smoking": "🚬",
+ "snow_capped_mountain": "🏔",
+ "soccer": "⚽",
+ "icecream": "🍦",
+ "soon": "🔜",
+ "arrow_lower_right": "↘",
+ "arrow_lower_left": "↙",
+ "speak_no_evil": "🙊",
+ "speaker": "🔈",
+ "mute": "🔇",
+ "sound": "🔉",
+ "loud_sound": "🔊",
+ "speaking_head_in_silhouette": "🗣",
+ "spiral_calendar_pad": "🗓",
+ "spiral_note_pad": "🗒",
+ "shell": "🐚",
+ "sweat_drops": "💦",
+ "u5272": "🈹",
+ "u5408": "🈴",
+ "u55b6": "🈺",
+ "u6307": "🈯",
+ "u6708": "🈷",
+ "u6709": "🈶",
+ "u6e80": "🈵",
+ "u7121": "🈚",
+ "u7533": "🈸",
+ "u7981": "🈲",
+ "u7a7a": "🈳",
+ "cl": "🆑",
+ "cool": "🆒",
+ "free": "🆓",
+ "id": "🆔",
+ "koko": "🈁",
+ "sa": "🈂",
+ "new": "🆕",
+ "ng": "🆖",
+ "ok": "🆗",
+ "sos": "🆘",
+ "up": "🆙",
+ "vs": "🆚",
+ "steam_locomotive": "🚂",
+ "ramen": "🍜",
+ "partly_sunny": "⛅",
+ "city_sunrise": "🌇",
+ "surfer": "🏄",
+ "swimmer": "🏊",
+ "shirt": "👕",
+ "tshirt": "👕",
+ "table_tennis_paddle_and_ball": "🏓",
+ "tea": "🍵",
+ "tv": "📺",
+ "three_button_mouse": "🖱",
+ "+1": "👍",
+ "thumbsup": "👍",
+ "__1": "👎",
+ "-1": "👎",
+ "thumbsdown": "👎",
+ "thunder_cloud_and_rain": "⛈",
+ "tiger2": "🐅",
+ "tophat": "🎩",
+ "top": "🔝",
+ "tm": "™",
+ "train2": "🚆",
+ "triangular_flag_on_post": "🚩",
+ "trident": "🔱",
+ "twisted_rightwards_arrows": "🔀",
+ "unamused": "😒",
+ "small_red_triangle": "🔺",
+ "arrow_up_small": "🔼",
+ "arrow_up_down": "↕",
+ "upside__down_face": "🙃",
+ "arrow_up": "⬆",
+ "v": "✌",
+ "vhs": "📼",
+ "wc": "🚾",
+ "ocean": "🌊",
+ "waving_black_flag": "🏴",
+ "wave": "👋",
+ "waving_white_flag": "🏳",
+ "moon": "🌔",
+ "scream_cat": "🙀",
+ "weary": "😩",
+ "weight_lifter": "🏋",
+ "whale2": "🐋",
+ "wheelchair": "♿",
+ "point_down": "👇",
+ "grey_exclamation": "❕",
+ "white_frowning_face": "☹",
+ "white_check_mark": "✅",
+ "point_left": "👈",
+ "white_medium_small_square": "◽",
+ "star": "⭐",
+ "grey_question": "❔",
+ "point_right": "👉",
+ "relaxed": "☺",
+ "white_sun_behind_cloud": "🌥",
+ "white_sun_behind_cloud_with_rain": "🌦",
+ "white_sun_with_small_cloud": "🌤",
+ "point_up_2": "👆",
+ "point_up": "☝",
+ "wind_blowing_face": "🌬",
+ "wink": "😉",
+ "wolf": "🐺",
+ "dancers": "👯",
+ "boot": "👢",
+ "womans_clothes": "👚",
+ "womans_hat": "👒",
+ "sandal": "👡",
+ "womens": "🚺",
+ "worried": "😟",
+ "gift": "🎁",
+ "zipper__mouth_face": "🤐",
+ "regional_indicator_a": "🇦",
+ "regional_indicator_b": "🇧",
+ "regional_indicator_c": "🇨",
+ "regional_indicator_d": "🇩",
+ "regional_indicator_e": "🇪",
+ "regional_indicator_f": "🇫",
+ "regional_indicator_g": "🇬",
+ "regional_indicator_h": "🇭",
+ "regional_indicator_i": "🇮",
+ "regional_indicator_j": "🇯",
+ "regional_indicator_k": "🇰",
+ "regional_indicator_l": "🇱",
+ "regional_indicator_m": "🇲",
+ "regional_indicator_n": "🇳",
+ "regional_indicator_o": "🇴",
+ "regional_indicator_p": "🇵",
+ "regional_indicator_q": "🇶",
+ "regional_indicator_r": "🇷",
+ "regional_indicator_s": "🇸",
+ "regional_indicator_t": "🇹",
+ "regional_indicator_u": "🇺",
+ "regional_indicator_v": "🇻",
+ "regional_indicator_w": "🇼",
+ "regional_indicator_x": "🇽",
+ "regional_indicator_y": "🇾",
+ "regional_indicator_z": "🇿",
+}
diff --git a/src/pip/_vendor/rich/_emoji_replace.py b/src/pip/_vendor/rich/_emoji_replace.py
new file mode 100644
index 000000000..bb2cafa18
--- /dev/null
+++ b/src/pip/_vendor/rich/_emoji_replace.py
@@ -0,0 +1,32 @@
+from typing import Callable, Match, Optional
+import re
+
+from ._emoji_codes import EMOJI
+
+
+_ReStringMatch = Match[str] # regex match object
+_ReSubCallable = Callable[[_ReStringMatch], str] # Callable invoked by re.sub
+_EmojiSubMethod = Callable[[_ReSubCallable, str], str] # Sub method of a compiled re
+
+
+def _emoji_replace(
+ text: str,
+ default_variant: Optional[str] = None,
+ _emoji_sub: _EmojiSubMethod = re.compile(r"(:(\S*?)(?:(?:\-)(emoji|text))?:)").sub,
+) -> str:
+ """Replace emoji code in text."""
+ get_emoji = EMOJI.__getitem__
+ variants = {"text": "\uFE0E", "emoji": "\uFE0F"}
+ get_variant = variants.get
+ default_variant_code = variants.get(default_variant, "") if default_variant else ""
+
+ def do_replace(match: Match[str]) -> str:
+ emoji_code, emoji_name, variant = match.groups()
+ try:
+ return get_emoji(emoji_name.lower()) + get_variant(
+ variant, default_variant_code
+ )
+ except KeyError:
+ return emoji_code
+
+ return _emoji_sub(do_replace, text)
diff --git a/src/pip/_vendor/rich/_export_format.py b/src/pip/_vendor/rich/_export_format.py
new file mode 100644
index 000000000..b79c13069
--- /dev/null
+++ b/src/pip/_vendor/rich/_export_format.py
@@ -0,0 +1,78 @@
+CONSOLE_HTML_FORMAT = """\
+<!DOCTYPE html>
+<head>
+<meta charset="UTF-8">
+<style>
+{stylesheet}
+body {{
+ color: {foreground};
+ background-color: {background};
+}}
+</style>
+</head>
+<html>
+<body>
+ <code>
+ <pre style="font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace">{code}</pre>
+ </code>
+</body>
+</html>
+"""
+
+CONSOLE_SVG_FORMAT = """\
+<svg class="rich-terminal" viewBox="0 0 {width} {height}" xmlns="http://www.w3.org/2000/svg">
+ <!-- Generated with Rich https://www.textualize.io -->
+ <style>
+
+ @font-face {{
+ font-family: "Fira Code";
+ src: local("FiraCode-Regular"),
+ url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
+ url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
+ font-style: normal;
+ font-weight: 400;
+ }}
+ @font-face {{
+ font-family: "Fira Code";
+ src: local("FiraCode-Bold"),
+ url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
+ url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
+ font-style: bold;
+ font-weight: 700;
+ }}
+
+ .{unique_id}-matrix {{
+ font-family: Fira Code, monospace;
+ font-size: {char_height}px;
+ line-height: {line_height}px;
+ font-variant-east-asian: full-width;
+ }}
+
+ .{unique_id}-title {{
+ font-size: 18px;
+ font-weight: bold;
+ font-family: arial;
+ }}
+
+ {styles}
+ </style>
+
+ <defs>
+ <clipPath id="{unique_id}-clip-terminal">
+ <rect x="0" y="0" width="{terminal_width}" height="{terminal_height}" />
+ </clipPath>
+ {lines}
+ </defs>
+
+ {chrome}
+ <g transform="translate({terminal_x}, {terminal_y})" clip-path="url(#{unique_id}-clip-terminal)">
+ {backgrounds}
+ <g class="{unique_id}-matrix">
+ {matrix}
+ </g>
+ </g>
+</svg>
+"""
+
+_SVG_FONT_FAMILY = "Rich Fira Code"
+_SVG_CLASSES_PREFIX = "rich-svg"
diff --git a/src/pip/_vendor/rich/_extension.py b/src/pip/_vendor/rich/_extension.py
new file mode 100644
index 000000000..cbd6da9be
--- /dev/null
+++ b/src/pip/_vendor/rich/_extension.py
@@ -0,0 +1,10 @@
+from typing import Any
+
+
+def load_ipython_extension(ip: Any) -> None: # pragma: no cover
+ # prevent circular import
+ from pip._vendor.rich.pretty import install
+ from pip._vendor.rich.traceback import install as tr_install
+
+ install()
+ tr_install()
diff --git a/src/pip/_vendor/rich/_inspect.py b/src/pip/_vendor/rich/_inspect.py
new file mode 100644
index 000000000..30446ceb3
--- /dev/null
+++ b/src/pip/_vendor/rich/_inspect.py
@@ -0,0 +1,270 @@
+from __future__ import absolute_import
+
+import inspect
+from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature
+from typing import Any, Collection, Iterable, Optional, Tuple, Type, Union
+
+from .console import Group, RenderableType
+from .control import escape_control_codes
+from .highlighter import ReprHighlighter
+from .jupyter import JupyterMixin
+from .panel import Panel
+from .pretty import Pretty
+from .table import Table
+from .text import Text, TextType
+
+
+def _first_paragraph(doc: str) -> str:
+ """Get the first paragraph from a docstring."""
+ paragraph, _, _ = doc.partition("\n\n")
+ return paragraph
+
+
+class Inspect(JupyterMixin):
+ """A renderable to inspect any Python Object.
+
+ Args:
+ obj (Any): An object to inspect.
+ title (str, optional): Title to display over inspect result, or None use type. Defaults to None.
+ help (bool, optional): Show full help text rather than just first paragraph. Defaults to False.
+ methods (bool, optional): Enable inspection of callables. Defaults to False.
+ docs (bool, optional): Also render doc strings. Defaults to True.
+ private (bool, optional): Show private attributes (beginning with underscore). Defaults to False.
+ dunder (bool, optional): Show attributes starting with double underscore. Defaults to False.
+ sort (bool, optional): Sort attributes alphabetically. Defaults to True.
+ all (bool, optional): Show all attributes. Defaults to False.
+ value (bool, optional): Pretty print value of object. Defaults to True.
+ """
+
+ def __init__(
+ self,
+ obj: Any,
+ *,
+ title: Optional[TextType] = None,
+ help: bool = False,
+ methods: bool = False,
+ docs: bool = True,
+ private: bool = False,
+ dunder: bool = False,
+ sort: bool = True,
+ all: bool = True,
+ value: bool = True,
+ ) -> None:
+ self.highlighter = ReprHighlighter()
+ self.obj = obj
+ self.title = title or self._make_title(obj)
+ if all:
+ methods = private = dunder = True
+ self.help = help
+ self.methods = methods
+ self.docs = docs or help
+ self.private = private or dunder
+ self.dunder = dunder
+ self.sort = sort
+ self.value = value
+
+ def _make_title(self, obj: Any) -> Text:
+ """Make a default title."""
+ title_str = (
+ str(obj)
+ if (isclass(obj) or callable(obj) or ismodule(obj))
+ else str(type(obj))
+ )
+ title_text = self.highlighter(title_str)
+ return title_text
+
+ def __rich__(self) -> Panel:
+ return Panel.fit(
+ Group(*self._render()),
+ title=self.title,
+ border_style="scope.border",
+ padding=(0, 1),
+ )
+
+ def _get_signature(self, name: str, obj: Any) -> Optional[Text]:
+ """Get a signature for a callable."""
+ try:
+ _signature = str(signature(obj)) + ":"
+ except ValueError:
+ _signature = "(...)"
+ except TypeError:
+ return None
+
+ source_filename: Optional[str] = None
+ try:
+ source_filename = getfile(obj)
+ except (OSError, TypeError):
+ # OSError is raised if obj has no source file, e.g. when defined in REPL.
+ pass
+
+ callable_name = Text(name, style="inspect.callable")
+ if source_filename:
+ callable_name.stylize(f"link file://{source_filename}")
+ signature_text = self.highlighter(_signature)
+
+ qualname = name or getattr(obj, "__qualname__", name)
+
+ # If obj is a module, there may be classes (which are callable) to display
+ if inspect.isclass(obj):
+ prefix = "class"
+ elif inspect.iscoroutinefunction(obj):
+ prefix = "async def"
+ else:
+ prefix = "def"
+
+ qual_signature = Text.assemble(
+ (f"{prefix} ", f"inspect.{prefix.replace(' ', '_')}"),
+ (qualname, "inspect.callable"),
+ signature_text,
+ )
+
+ return qual_signature
+
+ def _render(self) -> Iterable[RenderableType]:
+ """Render object."""
+
+ def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]:
+ key, (_error, value) = item
+ return (callable(value), key.strip("_").lower())
+
+ def safe_getattr(attr_name: str) -> Tuple[Any, Any]:
+ """Get attribute or any exception."""
+ try:
+ return (None, getattr(obj, attr_name))
+ except Exception as error:
+ return (error, None)
+
+ obj = self.obj
+ keys = dir(obj)
+ total_items = len(keys)
+ if not self.dunder:
+ keys = [key for key in keys if not key.startswith("__")]
+ if not self.private:
+ keys = [key for key in keys if not key.startswith("_")]
+ not_shown_count = total_items - len(keys)
+ items = [(key, safe_getattr(key)) for key in keys]
+ if self.sort:
+ items.sort(key=sort_items)
+
+ items_table = Table.grid(padding=(0, 1), expand=False)
+ items_table.add_column(justify="right")
+ add_row = items_table.add_row
+ highlighter = self.highlighter
+
+ if callable(obj):
+ signature = self._get_signature("", obj)
+ if signature is not None:
+ yield signature
+ yield ""
+
+ if self.docs:
+ _doc = self._get_formatted_doc(obj)
+ if _doc is not None:
+ doc_text = Text(_doc, style="inspect.help")
+ doc_text = highlighter(doc_text)
+ yield doc_text
+ yield ""
+
+ if self.value and not (isclass(obj) or callable(obj) or ismodule(obj)):
+ yield Panel(
+ Pretty(obj, indent_guides=True, max_length=10, max_string=60),
+ border_style="inspect.value.border",
+ )
+ yield ""
+
+ for key, (error, value) in items:
+ key_text = Text.assemble(
+ (
+ key,
+ "inspect.attr.dunder" if key.startswith("__") else "inspect.attr",
+ ),
+ (" =", "inspect.equals"),
+ )
+ if error is not None:
+ warning = key_text.copy()
+ warning.stylize("inspect.error")
+ add_row(warning, highlighter(repr(error)))
+ continue
+
+ if callable(value):
+ if not self.methods:
+ continue
+
+ _signature_text = self._get_signature(key, value)
+ if _signature_text is None:
+ add_row(key_text, Pretty(value, highlighter=highlighter))
+ else:
+ if self.docs:
+ docs = self._get_formatted_doc(value)
+ if docs is not None:
+ _signature_text.append("\n" if "\n" in docs else " ")
+ doc = highlighter(docs)
+ doc.stylize("inspect.doc")
+ _signature_text.append(doc)
+
+ add_row(key_text, _signature_text)
+ else:
+ add_row(key_text, Pretty(value, highlighter=highlighter))
+ if items_table.row_count:
+ yield items_table
+ elif not_shown_count:
+ yield Text.from_markup(
+ f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] "
+ f"Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options."
+ )
+
+ def _get_formatted_doc(self, object_: Any) -> Optional[str]:
+ """
+ Extract the docstring of an object, process it and returns it.
+ The processing consists in cleaning up the doctring's indentation,
+ taking only its 1st paragraph if `self.help` is not True,
+ and escape its control codes.
+
+ Args:
+ object_ (Any): the object to get the docstring from.
+
+ Returns:
+ Optional[str]: the processed docstring, or None if no docstring was found.
+ """
+ docs = getdoc(object_)
+ if docs is None:
+ return None
+ docs = cleandoc(docs).strip()
+ if not self.help:
+ docs = _first_paragraph(docs)
+ return escape_control_codes(docs)
+
+
+def get_object_types_mro(obj: Union[object, Type[Any]]) -> Tuple[type, ...]:
+ """Returns the MRO of an object's class, or of the object itself if it's a class."""
+ if not hasattr(obj, "__mro__"):
+ # N.B. we cannot use `if type(obj) is type` here because it doesn't work with
+ # some types of classes, such as the ones that use abc.ABCMeta.
+ obj = type(obj)
+ return getattr(obj, "__mro__", ())
+
+
+def get_object_types_mro_as_strings(obj: object) -> Collection[str]:
+ """
+ Returns the MRO of an object's class as full qualified names, or of the object itself if it's a class.
+
+ Examples:
+ `object_types_mro_as_strings(JSONDecoder)` will return `['json.decoder.JSONDecoder', 'builtins.object']`
+ """
+ return [
+ f'{getattr(type_, "__module__", "")}.{getattr(type_, "__qualname__", "")}'
+ for type_ in get_object_types_mro(obj)
+ ]
+
+
+def is_object_one_of_types(
+ obj: object, fully_qualified_types_names: Collection[str]
+) -> bool:
+ """
+ Returns `True` if the given object's class (or the object itself, if it's a class) has one of the
+ fully qualified names in its MRO.
+ """
+ for type_name in get_object_types_mro_as_strings(obj):
+ if type_name in fully_qualified_types_names:
+ return True
+ return False
diff --git a/src/pip/_vendor/rich/_log_render.py b/src/pip/_vendor/rich/_log_render.py
new file mode 100644
index 000000000..fc16c8443
--- /dev/null
+++ b/src/pip/_vendor/rich/_log_render.py
@@ -0,0 +1,94 @@
+from datetime import datetime
+from typing import Iterable, List, Optional, TYPE_CHECKING, Union, Callable
+
+
+from .text import Text, TextType
+
+if TYPE_CHECKING:
+ from .console import Console, ConsoleRenderable, RenderableType
+ from .table import Table
+
+FormatTimeCallable = Callable[[datetime], Text]
+
+
+class LogRender:
+ def __init__(
+ self,
+ show_time: bool = True,
+ show_level: bool = False,
+ show_path: bool = True,
+ time_format: Union[str, FormatTimeCallable] = "[%x %X]",
+ omit_repeated_times: bool = True,
+ level_width: Optional[int] = 8,
+ ) -> None:
+ self.show_time = show_time
+ self.show_level = show_level
+ self.show_path = show_path
+ self.time_format = time_format
+ self.omit_repeated_times = omit_repeated_times
+ self.level_width = level_width
+ self._last_time: Optional[Text] = None
+
+ def __call__(
+ self,
+ console: "Console",
+ renderables: Iterable["ConsoleRenderable"],
+ log_time: Optional[datetime] = None,
+ time_format: Optional[Union[str, FormatTimeCallable]] = None,
+ level: TextType = "",
+ path: Optional[str] = None,
+ line_no: Optional[int] = None,
+ link_path: Optional[str] = None,
+ ) -> "Table":
+ from .containers import Renderables
+ from .table import Table
+
+ output = Table.grid(padding=(0, 1))
+ output.expand = True
+ if self.show_time:
+ output.add_column(style="log.time")
+ if self.show_level:
+ output.add_column(style="log.level", width=self.level_width)
+ output.add_column(ratio=1, style="log.message", overflow="fold")
+ if self.show_path and path:
+ output.add_column(style="log.path")
+ row: List["RenderableType"] = []
+ if self.show_time:
+ log_time = log_time or console.get_datetime()
+ time_format = time_format or self.time_format
+ if callable(time_format):
+ log_time_display = time_format(log_time)
+ else:
+ log_time_display = Text(log_time.strftime(time_format))
+ if log_time_display == self._last_time and self.omit_repeated_times:
+ row.append(Text(" " * len(log_time_display)))
+ else:
+ row.append(log_time_display)
+ self._last_time = log_time_display
+ if self.show_level:
+ row.append(level)
+
+ row.append(Renderables(renderables))
+ if self.show_path and path:
+ path_text = Text()
+ path_text.append(
+ path, style=f"link file://{link_path}" if link_path else ""
+ )
+ if line_no:
+ path_text.append(":")
+ path_text.append(
+ f"{line_no}",
+ style=f"link file://{link_path}#{line_no}" if link_path else "",
+ )
+ row.append(path_text)
+
+ output.add_row(*row)
+ return output
+
+
+if __name__ == "__main__": # pragma: no cover
+ from pip._vendor.rich.console import Console
+
+ c = Console()
+ c.print("[on blue]Hello", justify="right")
+ c.log("[on blue]hello", justify="right")
diff --git a/src/pip/_vendor/rich/_loop.py b/src/pip/_vendor/rich/_loop.py
new file mode 100644
index 000000000..01c6cafbe
--- /dev/null
+++ b/src/pip/_vendor/rich/_loop.py
@@ -0,0 +1,43 @@
+from typing import Iterable, Tuple, TypeVar
+
+T = TypeVar("T")
+
+
+def loop_first(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
+ """Iterate and generate a tuple with a flag for first value."""
+ iter_values = iter(values)
+ try:
+ value = next(iter_values)
+ except StopIteration:
+ return
+ yield True, value
+ for value in iter_values:
+ yield False, value
+
+
+def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
+ """Iterate and generate a tuple with a flag for last value."""
+ iter_values = iter(values)
+ try:
+ previous_value = next(iter_values)
+ except StopIteration:
+ return
+ for value in iter_values:
+ yield False, previous_value
+ previous_value = value
+ yield True, previous_value
+
+
+def loop_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]:
+ """Iterate and generate a tuple with a flag for first and last value."""
+ iter_values = iter(values)
+ try:
+ previous_value = next(iter_values)
+ except StopIteration:
+ return
+ first = True
+ for value in iter_values:
+ yield first, False, previous_value
+ first = False
+ previous_value = value
+ yield first, True, previous_value
diff --git a/src/pip/_vendor/rich/_null_file.py b/src/pip/_vendor/rich/_null_file.py
new file mode 100644
index 000000000..49038bfcb
--- /dev/null
+++ b/src/pip/_vendor/rich/_null_file.py
@@ -0,0 +1,83 @@
+from types import TracebackType
+from typing import IO, Iterable, Iterator, List, Optional, Type
+
+
+class NullFile(IO[str]):
+
+ # TODO: "mode", "name" and "closed" are only required for Python 3.6.
+
+ @property
+ def mode(self) -> str:
+ return ""
+
+ @property
+ def name(self) -> str:
+ return "NullFile"
+
+ def closed(self) -> bool:
+ return False
+
+ def close(self) -> None:
+ pass
+
+ def isatty(self) -> bool:
+ return False
+
+ def read(self, __n: int = 1) -> str:
+ return ""
+
+ def readable(self) -> bool:
+ return False
+
+ def readline(self, __limit: int = 1) -> str:
+ return ""
+
+ def readlines(self, __hint: int = 1) -> List[str]:
+ return []
+
+ def seek(self, __offset: int, __whence: int = 1) -> int:
+ return 0
+
+ def seekable(self) -> bool:
+ return False
+
+ def tell(self) -> int:
+ return 0
+
+ def truncate(self, __size: Optional[int] = 1) -> int:
+ return 0
+
+ def writable(self) -> bool:
+ return False
+
+ def writelines(self, __lines: Iterable[str]) -> None:
+ pass
+
+ def __next__(self) -> str:
+ return ""
+
+ def __iter__(self) -> Iterator[str]:
+ return iter([""])
+
+ def __enter__(self) -> IO[str]:
+ pass
+
+ def __exit__(
+ self,
+ __t: Optional[Type[BaseException]],
+ __value: Optional[BaseException],
+ __traceback: Optional[TracebackType],
+ ) -> None:
+ pass
+
+ def write(self, text: str) -> int:
+ return 0
+
+ def flush(self) -> None:
+ pass
+
+ def fileno(self) -> int:
+ return -1
+
+
+NULL_FILE = NullFile()
diff --git a/src/pip/_vendor/rich/_palettes.py b/src/pip/_vendor/rich/_palettes.py
new file mode 100644
index 000000000..3c748d33e
--- /dev/null
+++ b/src/pip/_vendor/rich/_palettes.py
@@ -0,0 +1,309 @@
+from .palette import Palette
+
+
+# Taken from https://en.wikipedia.org/wiki/ANSI_escape_code (Windows 10 column)
+WINDOWS_PALETTE = Palette(
+ [
+ (12, 12, 12),
+ (197, 15, 31),
+ (19, 161, 14),
+ (193, 156, 0),
+ (0, 55, 218),
+ (136, 23, 152),
+ (58, 150, 221),
+ (204, 204, 204),
+ (118, 118, 118),
+ (231, 72, 86),
+ (22, 198, 12),
+ (249, 241, 165),
+ (59, 120, 255),
+ (180, 0, 158),
+ (97, 214, 214),
+ (242, 242, 242),
+ ]
+)
+
+# # The standard ansi colors (including bright variants)
+STANDARD_PALETTE = Palette(
+ [
+ (0, 0, 0),
+ (170, 0, 0),
+ (0, 170, 0),
+ (170, 85, 0),
+ (0, 0, 170),
+ (170, 0, 170),
+ (0, 170, 170),
+ (170, 170, 170),
+ (85, 85, 85),
+ (255, 85, 85),
+ (85, 255, 85),
+ (255, 255, 85),
+ (85, 85, 255),
+ (255, 85, 255),
+ (85, 255, 255),
+ (255, 255, 255),
+ ]
+)
+
+
+# The 256 color palette
+EIGHT_BIT_PALETTE = Palette(
+ [
+ (0, 0, 0),
+ (128, 0, 0),
+ (0, 128, 0),
+ (128, 128, 0),
+ (0, 0, 128),
+ (128, 0, 128),
+ (0, 128, 128),
+ (192, 192, 192),
+ (128, 128, 128),
+ (255, 0, 0),
+ (0, 255, 0),
+ (255, 255, 0),
+ (0, 0, 255),
+ (255, 0, 255),
+ (0, 255, 255),
+ (255, 255, 255),
+ (0, 0, 0),
+ (0, 0, 95),
+ (0, 0, 135),
+ (0, 0, 175),
+ (0, 0, 215),
+ (0, 0, 255),
+ (0, 95, 0),
+ (0, 95, 95),
+ (0, 95, 135),
+ (0, 95, 175),
+ (0, 95, 215),
+ (0, 95, 255),
+ (0, 135, 0),
+ (0, 135, 95),
+ (0, 135, 135),
+ (0, 135, 175),
+ (0, 135, 215),
+ (0, 135, 255),
+ (0, 175, 0),
+ (0, 175, 95),
+ (0, 175, 135),
+ (0, 175, 175),
+ (0, 175, 215),
+ (0, 175, 255),
+ (0, 215, 0),
+ (0, 215, 95),
+ (0, 215, 135),
+ (0, 215, 175),
+ (0, 215, 215),
+ (0, 215, 255),
+ (0, 255, 0),
+ (0, 255, 95),
+ (0, 255, 135),
+ (0, 255, 175),
+ (0, 255, 215),
+ (0, 255, 255),
+ (95, 0, 0),
+ (95, 0, 95),
+ (95, 0, 135),
+ (95, 0, 175),
+ (95, 0, 215),
+ (95, 0, 255),
+ (95, 95, 0),
+ (95, 95, 95),
+ (95, 95, 135),
+ (95, 95, 175),
+ (95, 95, 215),
+ (95, 95, 255),
+ (95, 135, 0),
+ (95, 135, 95),
+ (95, 135, 135),
+ (95, 135, 175),
+ (95, 135, 215),
+ (95, 135, 255),
+ (95, 175, 0),
+ (95, 175, 95),
+ (95, 175, 135),
+ (95, 175, 175),
+ (95, 175, 215),
+ (95, 175, 255),
+ (95, 215, 0),
+ (95, 215, 95),
+ (95, 215, 135),
+ (95, 215, 175),
+ (95, 215, 215),
+ (95, 215, 255),
+ (95, 255, 0),
+ (95, 255, 95),
+ (95, 255, 135),
+ (95, 255, 175),
+ (95, 255, 215),
+ (95, 255, 255),
+ (135, 0, 0),
+ (135, 0, 95),
+ (135, 0, 135),
+ (135, 0, 175),
+ (135, 0, 215),
+ (135, 0, 255),
+ (135, 95, 0),
+ (135, 95, 95),
+ (135, 95, 135),
+ (135, 95, 175),
+ (135, 95, 215),
+ (135, 95, 255),
+ (135, 135, 0),
+ (135, 135, 95),
+ (135, 135, 135),
+ (135, 135, 175),
+ (135, 135, 215),
+ (135, 135, 255),
+ (135, 175, 0),
+ (135, 175, 95),
+ (135, 175, 135),
+ (135, 175, 175),
+ (135, 175, 215),
+ (135, 175, 255),
+ (135, 215, 0),
+ (135, 215, 95),
+ (135, 215, 135),
+ (135, 215, 175),
+ (135, 215, 215),
+ (135, 215, 255),
+ (135, 255, 0),
+ (135, 255, 95),
+ (135, 255, 135),
+ (135, 255, 175),
+ (135, 255, 215),
+ (135, 255, 255),
+ (175, 0, 0),
+ (175, 0, 95),
+ (175, 0, 135),
+ (175, 0, 175),
+ (175, 0, 215),
+ (175, 0, 255),
+ (175, 95, 0),
+ (175, 95, 95),
+ (175, 95, 135),
+ (175, 95, 175),
+ (175, 95, 215),
+ (175, 95, 255),
+ (175, 135, 0),
+ (175, 135, 95),
+ (175, 135, 135),
+ (175, 135, 175),
+ (175, 135, 215),
+ (175, 135, 255),
+ (175, 175, 0),
+ (175, 175, 95),
+ (175, 175, 135),
+ (175, 175, 175),
+ (175, 175, 215),
+ (175, 175, 255),
+ (175, 215, 0),
+ (175, 215, 95),
+ (175, 215, 135),
+ (175, 215, 175),
+ (175, 215, 215),
+ (175, 215, 255),
+ (175, 255, 0),
+ (175, 255, 95),
+ (175, 255, 135),
+ (175, 255, 175),
+ (175, 255, 215),
+ (175, 255, 255),
+ (215, 0, 0),
+ (215, 0, 95),
+ (215, 0, 135),
+ (215, 0, 175),
+ (215, 0, 215),
+ (215, 0, 255),
+ (215, 95, 0),
+ (215, 95, 95),
+ (215, 95, 135),
+ (215, 95, 175),
+ (215, 95, 215),
+ (215, 95, 255),
+ (215, 135, 0),
+ (215, 135, 95),
+ (215, 135, 135),
+ (215, 135, 175),
+ (215, 135, 215),
+ (215, 135, 255),
+ (215, 175, 0),
+ (215, 175, 95),
+ (215, 175, 135),
+ (215, 175, 175),
+ (215, 175, 215),
+ (215, 175, 255),
+ (215, 215, 0),
+ (215, 215, 95),
+ (215, 215, 135),
+ (215, 215, 175),
+ (215, 215, 215),
+ (215, 215, 255),
+ (215, 255, 0),
+ (215, 255, 95),
+ (215, 255, 135),
+ (215, 255, 175),
+ (215, 255, 215),
+ (215, 255, 255),
+ (255, 0, 0),
+ (255, 0, 95),
+ (255, 0, 135),
+ (255, 0, 175),
+ (255, 0, 215),
+ (255, 0, 255),
+ (255, 95, 0),
+ (255, 95, 95),
+ (255, 95, 135),
+ (255, 95, 175),
+ (255, 95, 215),
+ (255, 95, 255),
+ (255, 135, 0),
+ (255, 135, 95),
+ (255, 135, 135),
+ (255, 135, 175),
+ (255, 135, 215),
+ (255, 135, 255),
+ (255, 175, 0),
+ (255, 175, 95),
+ (255, 175, 135),
+ (255, 175, 175),
+ (255, 175, 215),
+ (255, 175, 255),
+ (255, 215, 0),
+ (255, 215, 95),
+ (255, 215, 135),
+ (255, 215, 175),
+ (255, 215, 215),
+ (255, 215, 255),
+ (255, 255, 0),
+ (255, 255, 95),
+ (255, 255, 135),
+ (255, 255, 175),
+ (255, 255, 215),
+ (255, 255, 255),
+ (8, 8, 8),
+ (18, 18, 18),
+ (28, 28, 28),
+ (38, 38, 38),
+ (48, 48, 48),
+ (58, 58, 58),
+ (68, 68, 68),
+ (78, 78, 78),
+ (88, 88, 88),
+ (98, 98, 98),
+ (108, 108, 108),
+ (118, 118, 118),
+ (128, 128, 128),
+ (138, 138, 138),
+ (148, 148, 148),
+ (158, 158, 158),
+ (168, 168, 168),
+ (178, 178, 178),
+ (188, 188, 188),
+ (198, 198, 198),
+ (208, 208, 208),
+ (218, 218, 218),
+ (228, 228, 228),
+ (238, 238, 238),
+ ]
+)
diff --git a/src/pip/_vendor/rich/_pick.py b/src/pip/_vendor/rich/_pick.py
new file mode 100644
index 000000000..4f6d8b2d7
--- /dev/null
+++ b/src/pip/_vendor/rich/_pick.py
@@ -0,0 +1,17 @@
+from typing import Optional
+
+
+def pick_bool(*values: Optional[bool]) -> bool:
+ """Pick the first non-none bool or return the last value.
+
+ Args:
+ *values (bool): Any number of boolean or None values.
+
+ Returns:
+ bool: First non-none boolean.
+ """
+ assert values, "1 or more values required"
+ for value in values:
+ if value is not None:
+ return value
+ return bool(value)
diff --git a/src/pip/_vendor/rich/_ratio.py b/src/pip/_vendor/rich/_ratio.py
new file mode 100644
index 000000000..e8a3a674e
--- /dev/null
+++ b/src/pip/_vendor/rich/_ratio.py
@@ -0,0 +1,160 @@
+import sys
+from fractions import Fraction
+from math import ceil
+from typing import cast, List, Optional, Sequence
+
+if sys.version_info >= (3, 8):
+ from typing import Protocol
+else:
+ from pip._vendor.typing_extensions import Protocol # pragma: no cover
+
+
+class Edge(Protocol):
+ """Any object that defines an edge (such as Layout)."""
+
+ size: Optional[int] = None
+ ratio: int = 1
+ minimum_size: int = 1
+
+
+def ratio_resolve(total: int, edges: Sequence[Edge]) -> List[int]:
+ """Divide total space to satisfy size, ratio, and minimum_size, constraints.
+
+ The returned list of integers should add up to total in most cases, unless it is
+ impossible to satisfy all the constraints. For instance, if there are two edges
+ with a minimum size of 20 each and `total` is 30 then the returned list will be
+ greater than total. In practice, this would mean that a Layout object would
+ clip the rows that would overflow the screen height.
+
+ Args:
+ total (int): Total number of characters.
+ edges (List[Edge]): Edges within total space.
+
+ Returns:
+ List[int]: Number of characters for each edge.
+ """
+ # Size of edge or None for yet to be determined
+ sizes = [(edge.size or None) for edge in edges]
+
+ _Fraction = Fraction
+
+ # While any edges haven't been calculated
+ while None in sizes:
+ # Get flexible edges and index to map these back on to sizes list
+ flexible_edges = [
+ (index, edge)
+ for index, (size, edge) in enumerate(zip(sizes, edges))
+ if size is None
+ ]
+ # Remaining space in total
+ remaining = total - sum(size or 0 for size in sizes)
+ if remaining <= 0:
+ # No room for flexible edges
+ return [
+ ((edge.minimum_size or 1) if size is None else size)
+ for size, edge in zip(sizes, edges)
+ ]
+ # Calculate number of characters in a ratio portion
+ portion = _Fraction(
+ remaining, sum((edge.ratio or 1) for _, edge in flexible_edges)
+ )
+
+ # If any edges will be less than their minimum, replace size with the minimum
+ for index, edge in flexible_edges:
+ if portion * edge.ratio <= edge.minimum_size:
+ sizes[index] = edge.minimum_size
+ # New fixed size will invalidate calculations, so we need to repeat the process
+ break
+ else:
+ # Distribute flexible space and compensate for rounding error
+ # Since edge sizes can only be integers we need to add the remainder
+ # to the following line
+ remainder = _Fraction(0)
+ for index, edge in flexible_edges:
+ size, remainder = divmod(portion * edge.ratio + remainder, 1)
+ sizes[index] = size
+ break
+ # Sizes now contains integers only
+ return cast(List[int], sizes)
+
+
+def ratio_reduce(
+ total: int, ratios: List[int], maximums: List[int], values: List[int]
+) -> List[int]:
+ """Divide an integer total in to parts based on ratios.
+
+ Args:
+ total (int): The total to divide.
+ ratios (List[int]): A list of integer ratios.
+ maximums (List[int]): List of maximums values for each slot.
+ values (List[int]): List of values
+
+ Returns:
+ List[int]: A list of integers guaranteed to sum to total.
+ """
+ ratios = [ratio if _max else 0 for ratio, _max in zip(ratios, maximums)]
+ total_ratio = sum(ratios)
+ if not total_ratio:
+ return values[:]
+ total_remaining = total
+ result: List[int] = []
+ append = result.append
+ for ratio, maximum, value in zip(ratios, maximums, values):
+ if ratio and total_ratio > 0:
+ distributed = min(maximum, round(ratio * total_remaining / total_ratio))
+ append(value - distributed)
+ total_remaining -= distributed
+ total_ratio -= ratio
+ else:
+ append(value)
+ return result
+
+
+def ratio_distribute(
+ total: int, ratios: List[int], minimums: Optional[List[int]] = None
+) -> List[int]:
+ """Distribute an integer total in to parts based on ratios.
+
+ Args:
+ total (int): The total to divide.
+ ratios (List[int]): A list of integer ratios.
+ minimums (List[int]): List of minimum values for each slot.
+
+ Returns:
+ List[int]: A list of integers guaranteed to sum to total.
+ """
+ if minimums:
+ ratios = [ratio if _min else 0 for ratio, _min in zip(ratios, minimums)]
+ total_ratio = sum(ratios)
+ assert total_ratio > 0, "Sum of ratios must be > 0"
+
+ total_remaining = total
+ distributed_total: List[int] = []
+ append = distributed_total.append
+ if minimums is None:
+ _minimums = [0] * len(ratios)
+ else:
+ _minimums = minimums
+ for ratio, minimum in zip(ratios, _minimums):
+ if total_ratio > 0:
+ distributed = max(minimum, ceil(ratio * total_remaining / total_ratio))
+ else:
+ distributed = total_remaining
+ append(distributed)
+ total_ratio -= ratio
+ total_remaining -= distributed
+ return distributed_total
+
+
+if __name__ == "__main__":
+ from dataclasses import dataclass
+
+ @dataclass
+ class E:
+
+ size: Optional[int] = None
+ ratio: int = 1
+ minimum_size: int = 1
+
+ resolved = ratio_resolve(110, [E(None, 1, 1), E(None, 1, 1), E(None, 1, 1)])
+ print(sum(resolved))
diff --git a/src/pip/_vendor/rich/_spinners.py b/src/pip/_vendor/rich/_spinners.py
new file mode 100644
index 000000000..d0bb1fe75
--- /dev/null
+++ b/src/pip/_vendor/rich/_spinners.py
@@ -0,0 +1,482 @@
+"""
+Spinners are from:
+* cli-spinners:
+ MIT License
+ Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
+ 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.
+"""
+
+SPINNERS = {
+ "dots": {
+ "interval": 80,
+ "frames": "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏",
+ },
+ "dots2": {"interval": 80, "frames": "⣾⣽⣻⢿⡿⣟⣯⣷"},
+ "dots3": {
+ "interval": 80,
+ "frames": "⠋⠙⠚⠞⠖⠦⠴⠲⠳⠓",
+ },
+ "dots4": {
+ "interval": 80,
+ "frames": "⠄⠆⠇⠋⠙⠸⠰⠠⠰⠸⠙⠋⠇⠆",
+ },
+ "dots5": {
+ "interval": 80,
+ "frames": "⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓⠋",
+ },
+ "dots6": {
+ "interval": 80,
+ "frames": "⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠴⠲⠒⠂⠂⠒⠚⠙⠉⠁",
+ },
+ "dots7": {
+ "interval": 80,
+ "frames": "⠈⠉⠋⠓⠒⠐⠐⠒⠖⠦⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈",
+ },
+ "dots8": {
+ "interval": 80,
+ "frames": "⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈",
+ },
+ "dots9": {"interval": 80, "frames": "⢹⢺⢼⣸⣇⡧⡗⡏"},
+ "dots10": {"interval": 80, "frames": "⢄⢂⢁⡁⡈⡐⡠"},
+ "dots11": {"interval": 100, "frames": "⠁⠂⠄⡀⢀⠠⠐⠈"},
+ "dots12": {
+ "interval": 80,
+ "frames": [
+ "⢀⠀",
+ "⡀⠀",
+ "⠄⠀",
+ "⢂⠀",
+ "⡂⠀",
+ "⠅⠀",
+ "⢃⠀",
+ "⡃⠀",
+ "⠍⠀",
+ "⢋⠀",
+ "⡋⠀",
+ "⠍⠁",
+ "⢋⠁",
+ "⡋⠁",
+ "⠍⠉",
+ "⠋⠉",
+ "⠋⠉",
+ "⠉⠙",
+ "⠉⠙",
+ "⠉⠩",
+ "⠈⢙",
+ "⠈⡙",
+ "⢈⠩",
+ "⡀⢙",
+ "⠄⡙",
+ "⢂⠩",
+ "⡂⢘",
+ "⠅⡘",
+ "⢃⠨",
+ "⡃⢐",
+ "⠍⡐",
+ "⢋⠠",
+ "⡋⢀",
+ "⠍⡁",
+ "⢋⠁",
+ "⡋⠁",
+ "⠍⠉",
+ "⠋⠉",
+ "⠋⠉",
+ "⠉⠙",
+ "⠉⠙",
+ "⠉⠩",
+ "⠈⢙",
+ "⠈⡙",
+ "⠈⠩",
+ "⠀⢙",
+ "⠀⡙",
+ "⠀⠩",
+ "⠀⢘",
+ "⠀⡘",
+ "⠀⠨",
+ "⠀⢐",
+ "⠀⡐",
+ "⠀⠠",
+ "⠀⢀",
+ "⠀⡀",
+ ],
+ },
+ "dots8Bit": {
+ "interval": 80,
+ "frames": "⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙"
+ "⡚⡛⡜⡝⡞⡟⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻"
+ "⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕"
+ "⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷"
+ "⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿",
+ },
+ "line": {"interval": 130, "frames": ["-", "\\", "|", "/"]},
+ "line2": {"interval": 100, "frames": "⠂-–—–-"},
+ "pipe": {"interval": 100, "frames": "┤┘┴└├┌┬┐"},
+ "simpleDots": {"interval": 400, "frames": [". ", ".. ", "...", " "]},
+ "simpleDotsScrolling": {
+ "interval": 200,
+ "frames": [". ", ".. ", "...", " ..", " .", " "],
+ },
+ "star": {"interval": 70, "frames": "✶✸✹✺✹✷"},
+ "star2": {"interval": 80, "frames": "+x*"},
+ "flip": {
+ "interval": 70,
+ "frames": "___-``'´-___",
+ },
+ "hamburger": {"interval": 100, "frames": "☱☲☴"},
+ "growVertical": {
+ "interval": 120,
+ "frames": "▁▃▄▅▆▇▆▅▄▃",
+ },
+ "growHorizontal": {
+ "interval": 120,
+ "frames": "▏▎▍▌▋▊▉▊▋▌▍▎",
+ },
+ "balloon": {"interval": 140, "frames": " .oO@* "},
+ "balloon2": {"interval": 120, "frames": ".oO°Oo."},
+ "noise": {"interval": 100, "frames": "▓▒░"},
+ "bounce": {"interval": 120, "frames": "⠁⠂⠄⠂"},
+ "boxBounce": {"interval": 120, "frames": "▖▘▝▗"},
+ "boxBounce2": {"interval": 100, "frames": "▌▀▐▄"},
+ "triangle": {"interval": 50, "frames": "◢◣◤◥"},
+ "arc": {"interval": 100, "frames": "◜◠◝◞◡◟"},
+ "circle": {"interval": 120, "frames": "◡⊙◠"},
+ "squareCorners": {"interval": 180, "frames": "◰◳◲◱"},
+ "circleQuarters": {"interval": 120, "frames": "◴◷◶◵"},
+ "circleHalves": {"interval": 50, "frames": "◐◓◑◒"},
+ "squish": {"interval": 100, "frames": "╫╪"},
+ "toggle": {"interval": 250, "frames": "⊶⊷"},
+ "toggle2": {"interval": 80, "frames": "▫▪"},
+ "toggle3": {"interval": 120, "frames": "□■"},
+ "toggle4": {"interval": 100, "frames": "■□▪▫"},
+ "toggle5": {"interval": 100, "frames": "▮▯"},
+ "toggle6": {"interval": 300, "frames": "ဝ၀"},
+ "toggle7": {"interval": 80, "frames": "⦾⦿"},
+ "toggle8": {"interval": 100, "frames": "◍◌"},
+ "toggle9": {"interval": 100, "frames": "◉◎"},
+ "toggle10": {"interval": 100, "frames": "㊂㊀㊁"},
+ "toggle11": {"interval": 50, "frames": "⧇⧆"},
+ "toggle12": {"interval": 120, "frames": "☗☖"},
+ "toggle13": {"interval": 80, "frames": "=*-"},
+ "arrow": {"interval": 100, "frames": "←↖↑↗→↘↓↙"},
+ "arrow2": {
+ "interval": 80,
+ "frames": ["⬆️ ", "↗️ ", "➡️ ", "↘️ ", "⬇️ ", "↙️ ", "⬅️ ", "↖️ "],
+ },
+ "arrow3": {
+ "interval": 120,
+ "frames": ["▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸"],
+ },
+ "bouncingBar": {
+ "interval": 80,
+ "frames": [
+ "[ ]",
+ "[= ]",
+ "[== ]",
+ "[=== ]",
+ "[ ===]",
+ "[ ==]",
+ "[ =]",
+ "[ ]",
+ "[ =]",
+ "[ ==]",
+ "[ ===]",
+ "[====]",
+ "[=== ]",
+ "[== ]",
+ "[= ]",
+ ],
+ },
+ "bouncingBall": {
+ "interval": 80,
+ "frames": [
+ "( ● )",
+ "( ● )",
+ "( ● )",
+ "( ● )",
+ "( ●)",
+ "( ● )",
+ "( ● )",
+ "( ● )",
+ "( ● )",
+ "(● )",
+ ],
+ },
+ "smiley": {"interval": 200, "frames": ["😄 ", "😝 "]},
+ "monkey": {"interval": 300, "frames": ["🙈 ", "🙈 ", "🙉 ", "🙊 "]},
+ "hearts": {"interval": 100, "frames": ["💛 ", "💙 ", "💜 ", "💚 ", "❤️ "]},
+ "clock": {
+ "interval": 100,
+ "frames": [
+ "🕛 ",
+ "🕐 ",
+ "🕑 ",
+ "🕒 ",
+ "🕓 ",
+ "🕔 ",
+ "🕕 ",
+ "🕖 ",
+ "🕗 ",
+ "🕘 ",
+ "🕙 ",
+ "🕚 ",
+ ],
+ },
+ "earth": {"interval": 180, "frames": ["🌍 ", "🌎 ", "🌏 "]},
+ "material": {
+ "interval": 17,
+ "frames": [
+ "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
+ "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
+ "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
+ "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
+ "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
+ "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
+ "███████▁▁▁▁▁▁▁▁▁▁▁▁▁",
+ "████████▁▁▁▁▁▁▁▁▁▁▁▁",
+ "█████████▁▁▁▁▁▁▁▁▁▁▁",
+ "█████████▁▁▁▁▁▁▁▁▁▁▁",
+ "██████████▁▁▁▁▁▁▁▁▁▁",
+ "███████████▁▁▁▁▁▁▁▁▁",
+ "█████████████▁▁▁▁▁▁▁",
+ "██████████████▁▁▁▁▁▁",
+ "██████████████▁▁▁▁▁▁",
+ "▁██████████████▁▁▁▁▁",
+ "▁██████████████▁▁▁▁▁",
+ "▁██████████████▁▁▁▁▁",
+ "▁▁██████████████▁▁▁▁",
+ "▁▁▁██████████████▁▁▁",
+ "▁▁▁▁█████████████▁▁▁",
+ "▁▁▁▁██████████████▁▁",
+ "▁▁▁▁██████████████▁▁",
+ "▁▁▁▁▁██████████████▁",
+ "▁▁▁▁▁██████████████▁",
+ "▁▁▁▁▁██████████████▁",
+ "▁▁▁▁▁▁██████████████",
+ "▁▁▁▁▁▁██████████████",
+ "▁▁▁▁▁▁▁█████████████",
+ "▁▁▁▁▁▁▁█████████████",
+ "▁▁▁▁▁▁▁▁████████████",
+ "▁▁▁▁▁▁▁▁████████████",
+ "▁▁▁▁▁▁▁▁▁███████████",
+ "▁▁▁▁▁▁▁▁▁███████████",
+ "▁▁▁▁▁▁▁▁▁▁██████████",
+ "▁▁▁▁▁▁▁▁▁▁██████████",
+ "▁▁▁▁▁▁▁▁▁▁▁▁████████",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁███████",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████",
+ "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
+ "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
+ "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
+ "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
+ "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
+ "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
+ "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
+ "██████▁▁▁▁▁▁▁▁▁▁▁▁▁█",
+ "████████▁▁▁▁▁▁▁▁▁▁▁▁",
+ "█████████▁▁▁▁▁▁▁▁▁▁▁",
+ "█████████▁▁▁▁▁▁▁▁▁▁▁",
+ "█████████▁▁▁▁▁▁▁▁▁▁▁",
+ "█████████▁▁▁▁▁▁▁▁▁▁▁",
+ "███████████▁▁▁▁▁▁▁▁▁",
+ "████████████▁▁▁▁▁▁▁▁",
+ "████████████▁▁▁▁▁▁▁▁",
+ "██████████████▁▁▁▁▁▁",
+ "██████████████▁▁▁▁▁▁",
+ "▁██████████████▁▁▁▁▁",
+ "▁██████████████▁▁▁▁▁",
+ "▁▁▁█████████████▁▁▁▁",
+ "▁▁▁▁▁████████████▁▁▁",
+ "▁▁▁▁▁████████████▁▁▁",
+ "▁▁▁▁▁▁███████████▁▁▁",
+ "▁▁▁▁▁▁▁▁█████████▁▁▁",
+ "▁▁▁▁▁▁▁▁█████████▁▁▁",
+ "▁▁▁▁▁▁▁▁▁█████████▁▁",
+ "▁▁▁▁▁▁▁▁▁█████████▁▁",
+ "▁▁▁▁▁▁▁▁▁▁█████████▁",
+ "▁▁▁▁▁▁▁▁▁▁▁████████▁",
+ "▁▁▁▁▁▁▁▁▁▁▁████████▁",
+ "▁▁▁▁▁▁▁▁▁▁▁▁███████▁",
+ "▁▁▁▁▁▁▁▁▁▁▁▁███████▁",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁███████",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁███████",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
+ "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
+ ],
+ },
+ "moon": {
+ "interval": 80,
+ "frames": ["🌑 ", "🌒 ", "🌓 ", "🌔 ", "🌕 ", "🌖 ", "🌗 ", "🌘 "],
+ },
+ "runner": {"interval": 140, "frames": ["🚶 ", "🏃 "]},
+ "pong": {
+ "interval": 80,
+ "frames": [
+ "▐⠂ ▌",
+ "▐⠈ ▌",
+ "▐ ⠂ ▌",
+ "▐ ⠠ ▌",
+ "▐ ⡀ ▌",
+ "▐ ⠠ ▌",
+ "▐ ⠂ ▌",
+ "▐ ⠈ ▌",
+ "▐ ⠂ ▌",
+ "▐ ⠠ ▌",
+ "▐ ⡀ ▌",
+ "▐ ⠠ ▌",
+ "▐ ⠂ ▌",
+ "▐ ⠈ ▌",
+ "▐ ⠂▌",
+ "▐ ⠠▌",
+ "▐ ⡀▌",
+ "▐ ⠠ ▌",
+ "▐ ⠂ ▌",
+ "▐ ⠈ ▌",
+ "▐ ⠂ ▌",
+ "▐ ⠠ ▌",
+ "▐ ⡀ ▌",
+ "▐ ⠠ ▌",
+ "▐ ⠂ ▌",
+ "▐ ⠈ ▌",
+ "▐ ⠂ ▌",
+ "▐ ⠠ ▌",
+ "▐ ⡀ ▌",
+ "▐⠠ ▌",
+ ],
+ },
+ "shark": {
+ "interval": 120,
+ "frames": [
+ "▐|\\____________▌",
+ "▐_|\\___________▌",
+ "▐__|\\__________▌",
+ "▐___|\\_________▌",
+ "▐____|\\________▌",
+ "▐_____|\\_______▌",
+ "▐______|\\______▌",
+ "▐_______|\\_____▌",
+ "▐________|\\____▌",
+ "▐_________|\\___▌",
+ "▐__________|\\__▌",
+ "▐___________|\\_▌",
+ "▐____________|\\▌",
+ "▐____________/|▌",
+ "▐___________/|_▌",
+ "▐__________/|__▌",
+ "▐_________/|___▌",
+ "▐________/|____▌",
+ "▐_______/|_____▌",
+ "▐______/|______▌",
+ "▐_____/|_______▌",
+ "▐____/|________▌",
+ "▐___/|_________▌",
+ "▐__/|__________▌",
+ "▐_/|___________▌",
+ "▐/|____________▌",
+ ],
+ },
+ "dqpb": {"interval": 100, "frames": "dqpb"},
+ "weather": {
+ "interval": 100,
+ "frames": [
+ "☀️ ",
+ "☀️ ",
+ "☀️ ",
+ "🌤 ",
+ "⛅️ ",
+ "🌥 ",
+ "☁️ ",
+ "🌧 ",
+ "🌨 ",
+ "🌧 ",
+ "🌨 ",
+ "🌧 ",
+ "🌨 ",
+ "⛈ ",
+ "🌨 ",
+ "🌧 ",
+ "🌨 ",
+ "☁️ ",
+ "🌥 ",
+ "⛅️ ",
+ "🌤 ",
+ "☀️ ",
+ "☀️ ",
+ ],
+ },
+ "christmas": {"interval": 400, "frames": "🌲🎄"},
+ "grenade": {
+ "interval": 80,
+ "frames": [
+ "، ",
+ "′ ",
+ " ´ ",
+ " ‾ ",
+ " ⸌",
+ " ⸊",
+ " |",
+ " ⁎",
+ " ⁕",
+ " ෴ ",
+ " ⁓",
+ " ",
+ " ",
+ " ",
+ ],
+ },
+ "point": {"interval": 125, "frames": ["∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"]},
+ "layer": {"interval": 150, "frames": "-=≡"},
+ "betaWave": {
+ "interval": 80,
+ "frames": [
+ "ρββββββ",
+ "βρβββββ",
+ "ββρββββ",
+ "βββρβββ",
+ "ββββρββ",
+ "βββββρβ",
+ "ββββββρ",
+ ],
+ },
+ "aesthetic": {
+ "interval": 80,
+ "frames": [
+ "▰▱▱▱▱▱▱",
+ "▰▰▱▱▱▱▱",
+ "▰▰▰▱▱▱▱",
+ "▰▰▰▰▱▱▱",
+ "▰▰▰▰▰▱▱",
+ "▰▰▰▰▰▰▱",
+ "▰▰▰▰▰▰▰",
+ "▰▱▱▱▱▱▱",
+ ],
+ },
+}
diff --git a/src/pip/_vendor/rich/_stack.py b/src/pip/_vendor/rich/_stack.py
new file mode 100644
index 000000000..194564e76
--- /dev/null
+++ b/src/pip/_vendor/rich/_stack.py
@@ -0,0 +1,16 @@
+from typing import List, TypeVar
+
+T = TypeVar("T")
+
+
+class Stack(List[T]):
+ """A small shim over builtin list."""
+
+ @property
+ def top(self) -> T:
+ """Get top of stack."""
+ return self[-1]
+
+ def push(self, item: T) -> None:
+ """Push an item on to the stack (append in stack nomenclature)."""
+ self.append(item)
diff --git a/src/pip/_vendor/rich/_timer.py b/src/pip/_vendor/rich/_timer.py
new file mode 100644
index 000000000..a2ca6be03
--- /dev/null
+++ b/src/pip/_vendor/rich/_timer.py
@@ -0,0 +1,19 @@
+"""
+Timer context manager, only used in debug.
+
+"""
+
+from time import time
+
+import contextlib
+from typing import Generator
+
+
+@contextlib.contextmanager
+def timer(subject: str = "time") -> Generator[None, None, None]:
+ """print the elapsed time. (only used in debugging)"""
+ start = time()
+ yield
+ elapsed = time() - start
+ elapsed_ms = elapsed * 1000
+ print(f"{subject} elapsed {elapsed_ms:.1f}ms")
diff --git a/src/pip/_vendor/rich/_win32_console.py b/src/pip/_vendor/rich/_win32_console.py
new file mode 100644
index 000000000..81b108290
--- /dev/null
+++ b/src/pip/_vendor/rich/_win32_console.py
@@ -0,0 +1,662 @@
+"""Light wrapper around the Win32 Console API - this module should only be imported on Windows
+
+The API that this module wraps is documented at https://docs.microsoft.com/en-us/windows/console/console-functions
+"""
+import ctypes
+import sys
+from typing import Any
+
+windll: Any = None
+if sys.platform == "win32":
+ windll = ctypes.LibraryLoader(ctypes.WinDLL)
+else:
+ raise ImportError(f"{__name__} can only be imported on Windows")
+
+import time
+from ctypes import Structure, byref, wintypes
+from typing import IO, NamedTuple, Type, cast
+
+from pip._vendor.rich.color import ColorSystem
+from pip._vendor.rich.style import Style
+
+STDOUT = -11
+ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
+
+COORD = wintypes._COORD
+
+
+class LegacyWindowsError(Exception):
+ pass
+
+
+class WindowsCoordinates(NamedTuple):
+ """Coordinates in the Windows Console API are (y, x), not (x, y).
+ This class is intended to prevent that confusion.
+ Rows and columns are indexed from 0.
+ This class can be used in place of wintypes._COORD in arguments and argtypes.
+ """
+
+ row: int
+ col: int
+
+ @classmethod
+ def from_param(cls, value: "WindowsCoordinates") -> COORD:
+ """Converts a WindowsCoordinates into a wintypes _COORD structure.
+ This classmethod is internally called by ctypes to perform the conversion.
+
+ Args:
+ value (WindowsCoordinates): The input coordinates to convert.
+
+ Returns:
+ wintypes._COORD: The converted coordinates struct.
+ """
+ return COORD(value.col, value.row)
+
+
+class CONSOLE_SCREEN_BUFFER_INFO(Structure):
+ _fields_ = [
+ ("dwSize", COORD),
+ ("dwCursorPosition", COORD),
+ ("wAttributes", wintypes.WORD),
+ ("srWindow", wintypes.SMALL_RECT),
+ ("dwMaximumWindowSize", COORD),
+ ]
+
+
+class CONSOLE_CURSOR_INFO(ctypes.Structure):
+ _fields_ = [("dwSize", wintypes.DWORD), ("bVisible", wintypes.BOOL)]
+
+
+_GetStdHandle = windll.kernel32.GetStdHandle
+_GetStdHandle.argtypes = [
+ wintypes.DWORD,
+]
+_GetStdHandle.restype = wintypes.HANDLE
+
+
+def GetStdHandle(handle: int = STDOUT) -> wintypes.HANDLE:
+ """Retrieves a handle to the specified standard device (standard input, standard output, or standard error).
+
+ Args:
+ handle (int): Integer identifier for the handle. Defaults to -11 (stdout).
+
+ Returns:
+ wintypes.HANDLE: The handle
+ """
+ return cast(wintypes.HANDLE, _GetStdHandle(handle))
+
+
+_GetConsoleMode = windll.kernel32.GetConsoleMode
+_GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD]
+_GetConsoleMode.restype = wintypes.BOOL
+
+
+def GetConsoleMode(std_handle: wintypes.HANDLE) -> int:
+ """Retrieves the current input mode of a console's input buffer
+ or the current output mode of a console screen buffer.
+
+ Args:
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
+
+ Raises:
+ LegacyWindowsError: If any error occurs while calling the Windows console API.
+
+ Returns:
+ int: Value representing the current console mode as documented at
+ https://docs.microsoft.com/en-us/windows/console/getconsolemode#parameters
+ """
+
+ console_mode = wintypes.DWORD()
+ success = bool(_GetConsoleMode(std_handle, console_mode))
+ if not success:
+ raise LegacyWindowsError("Unable to get legacy Windows Console Mode")
+ return console_mode.value
+
+
+_FillConsoleOutputCharacterW = windll.kernel32.FillConsoleOutputCharacterW
+_FillConsoleOutputCharacterW.argtypes = [
+ wintypes.HANDLE,
+ ctypes.c_char,
+ wintypes.DWORD,
+ cast(Type[COORD], WindowsCoordinates),
+ ctypes.POINTER(wintypes.DWORD),
+]
+_FillConsoleOutputCharacterW.restype = wintypes.BOOL
+
+
+def FillConsoleOutputCharacter(
+ std_handle: wintypes.HANDLE,
+ char: str,
+ length: int,
+ start: WindowsCoordinates,
+) -> int:
+ """Writes a character to the console screen buffer a specified number of times, beginning at the specified coordinates.
+
+ Args:
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
+ char (str): The character to write. Must be a string of length 1.
+ length (int): The number of times to write the character.
+ start (WindowsCoordinates): The coordinates to start writing at.
+
+ Returns:
+ int: The number of characters written.
+ """
+ character = ctypes.c_char(char.encode())
+ num_characters = wintypes.DWORD(length)
+ num_written = wintypes.DWORD(0)
+ _FillConsoleOutputCharacterW(
+ std_handle,
+ character,
+ num_characters,
+ start,
+ byref(num_written),
+ )
+ return num_written.value
+
+
+_FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute
+_FillConsoleOutputAttribute.argtypes = [
+ wintypes.HANDLE,
+ wintypes.WORD,
+ wintypes.DWORD,
+ cast(Type[COORD], WindowsCoordinates),
+ ctypes.POINTER(wintypes.DWORD),
+]
+_FillConsoleOutputAttribute.restype = wintypes.BOOL
+
+
+def FillConsoleOutputAttribute(
+ std_handle: wintypes.HANDLE,
+ attributes: int,
+ length: int,
+ start: WindowsCoordinates,
+) -> int:
+ """Sets the character attributes for a specified number of character cells,
+ beginning at the specified coordinates in a screen buffer.
+
+ Args:
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
+ attributes (int): Integer value representing the foreground and background colours of the cells.
+ length (int): The number of cells to set the output attribute of.
+ start (WindowsCoordinates): The coordinates of the first cell whose attributes are to be set.
+
+ Returns:
+ int: The number of cells whose attributes were actually set.
+ """
+ num_cells = wintypes.DWORD(length)
+ style_attrs = wintypes.WORD(attributes)
+ num_written = wintypes.DWORD(0)
+ _FillConsoleOutputAttribute(
+ std_handle, style_attrs, num_cells, start, byref(num_written)
+ )
+ return num_written.value
+
+
+_SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
+_SetConsoleTextAttribute.argtypes = [
+ wintypes.HANDLE,
+ wintypes.WORD,
+]
+_SetConsoleTextAttribute.restype = wintypes.BOOL
+
+
+def SetConsoleTextAttribute(
+ std_handle: wintypes.HANDLE, attributes: wintypes.WORD
+) -> bool:
+ """Set the colour attributes for all text written after this function is called.
+
+ Args:
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
+ attributes (int): Integer value representing the foreground and background colours.
+
+
+ Returns:
+ bool: True if the attribute was set successfully, otherwise False.
+ """
+ return bool(_SetConsoleTextAttribute(std_handle, attributes))
+
+
+_GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
+_GetConsoleScreenBufferInfo.argtypes = [
+ wintypes.HANDLE,
+ ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO),
+]
+_GetConsoleScreenBufferInfo.restype = wintypes.BOOL
+
+
+def GetConsoleScreenBufferInfo(
+ std_handle: wintypes.HANDLE,
+) -> CONSOLE_SCREEN_BUFFER_INFO:
+ """Retrieves information about the specified console screen buffer.
+
+ Args:
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
+
+ Returns:
+ CONSOLE_SCREEN_BUFFER_INFO: A CONSOLE_SCREEN_BUFFER_INFO ctype struct contain information about
+ screen size, cursor position, colour attributes, and more."""
+ console_screen_buffer_info = CONSOLE_SCREEN_BUFFER_INFO()
+ _GetConsoleScreenBufferInfo(std_handle, byref(console_screen_buffer_info))
+ return console_screen_buffer_info
+
+
+_SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition
+_SetConsoleCursorPosition.argtypes = [
+ wintypes.HANDLE,
+ cast(Type[COORD], WindowsCoordinates),
+]
+_SetConsoleCursorPosition.restype = wintypes.BOOL
+
+
+def SetConsoleCursorPosition(
+ std_handle: wintypes.HANDLE, coords: WindowsCoordinates
+) -> bool:
+ """Set the position of the cursor in the console screen
+
+ Args:
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
+ coords (WindowsCoordinates): The coordinates to move the cursor to.
+
+ Returns:
+ bool: True if the function succeeds, otherwise False.
+ """
+ return bool(_SetConsoleCursorPosition(std_handle, coords))
+
+
+_GetConsoleCursorInfo = windll.kernel32.GetConsoleCursorInfo
+_GetConsoleCursorInfo.argtypes = [
+ wintypes.HANDLE,
+ ctypes.POINTER(CONSOLE_CURSOR_INFO),
+]
+_GetConsoleCursorInfo.restype = wintypes.BOOL
+
+
+def GetConsoleCursorInfo(
+ std_handle: wintypes.HANDLE, cursor_info: CONSOLE_CURSOR_INFO
+) -> bool:
+ """Get the cursor info - used to get cursor visibility and width
+
+ Args:
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
+ cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct that receives information
+ about the console's cursor.
+
+ Returns:
+ bool: True if the function succeeds, otherwise False.
+ """
+ return bool(_GetConsoleCursorInfo(std_handle, byref(cursor_info)))
+
+
+_SetConsoleCursorInfo = windll.kernel32.SetConsoleCursorInfo
+_SetConsoleCursorInfo.argtypes = [
+ wintypes.HANDLE,
+ ctypes.POINTER(CONSOLE_CURSOR_INFO),
+]
+_SetConsoleCursorInfo.restype = wintypes.BOOL
+
+
+def SetConsoleCursorInfo(
+ std_handle: wintypes.HANDLE, cursor_info: CONSOLE_CURSOR_INFO
+) -> bool:
+ """Set the cursor info - used for adjusting cursor visibility and width
+
+ Args:
+ std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.
+ cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct containing the new cursor info.
+
+ Returns:
+ bool: True if the function succeeds, otherwise False.
+ """
+ return bool(_SetConsoleCursorInfo(std_handle, byref(cursor_info)))
+
+
+_SetConsoleTitle = windll.kernel32.SetConsoleTitleW
+_SetConsoleTitle.argtypes = [wintypes.LPCWSTR]
+_SetConsoleTitle.restype = wintypes.BOOL
+
+
+def SetConsoleTitle(title: str) -> bool:
+ """Sets the title of the current console window
+
+ Args:
+ title (str): The new title of the console window.
+
+ Returns:
+ bool: True if the function succeeds, otherwise False.
+ """
+ return bool(_SetConsoleTitle(title))
+
+
+class LegacyWindowsTerm:
+ """This class allows interaction with the legacy Windows Console API. It should only be used in the context
+ of environments where virtual terminal processing is not available. However, if it is used in a Windows environment,
+ the entire API should work.
+
+ Args:
+ file (IO[str]): The file which the Windows Console API HANDLE is retrieved from, defaults to sys.stdout.
+ """
+
+ BRIGHT_BIT = 8
+
+ # Indices are ANSI color numbers, values are the corresponding Windows Console API color numbers
+ ANSI_TO_WINDOWS = [
+ 0, # black The Windows colours are defined in wincon.h as follows:
+ 4, # red define FOREGROUND_BLUE 0x0001 -- 0000 0001
+ 2, # green define FOREGROUND_GREEN 0x0002 -- 0000 0010
+ 6, # yellow define FOREGROUND_RED 0x0004 -- 0000 0100
+ 1, # blue define FOREGROUND_INTENSITY 0x0008 -- 0000 1000
+ 5, # magenta define BACKGROUND_BLUE 0x0010 -- 0001 0000
+ 3, # cyan define BACKGROUND_GREEN 0x0020 -- 0010 0000
+ 7, # white define BACKGROUND_RED 0x0040 -- 0100 0000
+ 8, # bright black (grey) define BACKGROUND_INTENSITY 0x0080 -- 1000 0000
+ 12, # bright red
+ 10, # bright green
+ 14, # bright yellow
+ 9, # bright blue
+ 13, # bright magenta
+ 11, # bright cyan
+ 15, # bright white
+ ]
+
+ def __init__(self, file: "IO[str]") -> None:
+ handle = GetStdHandle(STDOUT)
+ self._handle = handle
+ default_text = GetConsoleScreenBufferInfo(handle).wAttributes
+ self._default_text = default_text
+
+ self._default_fore = default_text & 7
+ self._default_back = (default_text >> 4) & 7
+ self._default_attrs = self._default_fore | (self._default_back << 4)
+
+ self._file = file
+ self.write = file.write
+ self.flush = file.flush
+
+ @property
+ def cursor_position(self) -> WindowsCoordinates:
+ """Returns the current position of the cursor (0-based)
+
+ Returns:
+ WindowsCoordinates: The current cursor position.
+ """
+ coord: COORD = GetConsoleScreenBufferInfo(self._handle).dwCursorPosition
+ return WindowsCoordinates(row=cast(int, coord.Y), col=cast(int, coord.X))
+
+ @property
+ def screen_size(self) -> WindowsCoordinates:
+ """Returns the current size of the console screen buffer, in character columns and rows
+
+ Returns:
+ WindowsCoordinates: The width and height of the screen as WindowsCoordinates.
+ """
+ screen_size: COORD = GetConsoleScreenBufferInfo(self._handle).dwSize
+ return WindowsCoordinates(
+ row=cast(int, screen_size.Y), col=cast(int, screen_size.X)
+ )
+
+ def write_text(self, text: str) -> None:
+ """Write text directly to the terminal without any modification of styles
+
+ Args:
+ text (str): The text to write to the console
+ """
+ self.write(text)
+ self.flush()
+
+ def write_styled(self, text: str, style: Style) -> None:
+ """Write styled text to the terminal.
+
+ Args:
+ text (str): The text to write
+ style (Style): The style of the text
+ """
+ color = style.color
+ bgcolor = style.bgcolor
+ if style.reverse:
+ color, bgcolor = bgcolor, color
+
+ if color:
+ fore = color.downgrade(ColorSystem.WINDOWS).number
+ fore = fore if fore is not None else 7 # Default to ANSI 7: White
+ if style.bold:
+ fore = fore | self.BRIGHT_BIT
+ if style.dim:
+ fore = fore & ~self.BRIGHT_BIT
+ fore = self.ANSI_TO_WINDOWS[fore]
+ else:
+ fore = self._default_fore
+
+ if bgcolor:
+ back = bgcolor.downgrade(ColorSystem.WINDOWS).number
+ back = back if back is not None else 0 # Default to ANSI 0: Black
+ back = self.ANSI_TO_WINDOWS[back]
+ else:
+ back = self._default_back
+
+ assert fore is not None
+ assert back is not None
+
+ SetConsoleTextAttribute(
+ self._handle, attributes=ctypes.c_ushort(fore | (back << 4))
+ )
+ self.write_text(text)
+ SetConsoleTextAttribute(self._handle, attributes=self._default_text)
+
+ def move_cursor_to(self, new_position: WindowsCoordinates) -> None:
+ """Set the position of the cursor
+
+ Args:
+ new_position (WindowsCoordinates): The WindowsCoordinates representing the new position of the cursor.
+ """
+ if new_position.col < 0 or new_position.row < 0:
+ return
+ SetConsoleCursorPosition(self._handle, coords=new_position)
+
+ def erase_line(self) -> None:
+ """Erase all content on the line the cursor is currently located at"""
+ screen_size = self.screen_size
+ cursor_position = self.cursor_position
+ cells_to_erase = screen_size.col
+ start_coordinates = WindowsCoordinates(row=cursor_position.row, col=0)
+ FillConsoleOutputCharacter(
+ self._handle, " ", length=cells_to_erase, start=start_coordinates
+ )
+ FillConsoleOutputAttribute(
+ self._handle,
+ self._default_attrs,
+ length=cells_to_erase,
+ start=start_coordinates,
+ )
+
+ def erase_end_of_line(self) -> None:
+ """Erase all content from the cursor position to the end of that line"""
+ cursor_position = self.cursor_position
+ cells_to_erase = self.screen_size.col - cursor_position.col
+ FillConsoleOutputCharacter(
+ self._handle, " ", length=cells_to_erase, start=cursor_position
+ )
+ FillConsoleOutputAttribute(
+ self._handle,
+ self._default_attrs,
+ length=cells_to_erase,
+ start=cursor_position,
+ )
+
+ def erase_start_of_line(self) -> None:
+ """Erase all content from the cursor position to the start of that line"""
+ row, col = self.cursor_position
+ start = WindowsCoordinates(row, 0)
+ FillConsoleOutputCharacter(self._handle, " ", length=col, start=start)
+ FillConsoleOutputAttribute(
+ self._handle, self._default_attrs, length=col, start=start
+ )
+
+ def move_cursor_up(self) -> None:
+ """Move the cursor up a single cell"""
+ cursor_position = self.cursor_position
+ SetConsoleCursorPosition(
+ self._handle,
+ coords=WindowsCoordinates(
+ row=cursor_position.row - 1, col=cursor_position.col
+ ),
+ )
+
+ def move_cursor_down(self) -> None:
+ """Move the cursor down a single cell"""
+ cursor_position = self.cursor_position
+ SetConsoleCursorPosition(
+ self._handle,
+ coords=WindowsCoordinates(
+ row=cursor_position.row + 1,
+ col=cursor_position.col,
+ ),
+ )
+
+ def move_cursor_forward(self) -> None:
+ """Move the cursor forward a single cell. Wrap to the next line if required."""
+ row, col = self.cursor_position
+ if col == self.screen_size.col - 1:
+ row += 1
+ col = 0
+ else:
+ col += 1
+ SetConsoleCursorPosition(
+ self._handle, coords=WindowsCoordinates(row=row, col=col)
+ )
+
+ def move_cursor_to_column(self, column: int) -> None:
+ """Move cursor to the column specified by the zero-based column index, staying on the same row
+
+ Args:
+ column (int): The zero-based column index to move the cursor to.
+ """
+ row, _ = self.cursor_position
+ SetConsoleCursorPosition(self._handle, coords=WindowsCoordinates(row, column))
+
+ def move_cursor_backward(self) -> None:
+ """Move the cursor backward a single cell. Wrap to the previous line if required."""
+ row, col = self.cursor_position
+ if col == 0:
+ row -= 1
+ col = self.screen_size.col - 1
+ else:
+ col -= 1
+ SetConsoleCursorPosition(
+ self._handle, coords=WindowsCoordinates(row=row, col=col)
+ )
+
+ def hide_cursor(self) -> None:
+ """Hide the cursor"""
+ current_cursor_size = self._get_cursor_size()
+ invisible_cursor = CONSOLE_CURSOR_INFO(dwSize=current_cursor_size, bVisible=0)
+ SetConsoleCursorInfo(self._handle, cursor_info=invisible_cursor)
+
+ def show_cursor(self) -> None:
+ """Show the cursor"""
+ current_cursor_size = self._get_cursor_size()
+ visible_cursor = CONSOLE_CURSOR_INFO(dwSize=current_cursor_size, bVisible=1)
+ SetConsoleCursorInfo(self._handle, cursor_info=visible_cursor)
+
+ def set_title(self, title: str) -> None:
+ """Set the title of the terminal window
+
+ Args:
+ title (str): The new title of the console window
+ """
+ assert len(title) < 255, "Console title must be less than 255 characters"
+ SetConsoleTitle(title)
+
+ def _get_cursor_size(self) -> int:
+ """Get the percentage of the character cell that is filled by the cursor"""
+ cursor_info = CONSOLE_CURSOR_INFO()
+ GetConsoleCursorInfo(self._handle, cursor_info=cursor_info)
+ return int(cursor_info.dwSize)
+
+
+if __name__ == "__main__":
+ handle = GetStdHandle()
+
+ from pip._vendor.rich.console import Console
+
+ console = Console()
+
+ term = LegacyWindowsTerm(sys.stdout)
+ term.set_title("Win32 Console Examples")
+
+ style = Style(color="black", bgcolor="red")
+
+ heading = Style.parse("black on green")
+
+ # Check colour output
+ console.rule("Checking colour output")
+ console.print("[on red]on red!")
+ console.print("[blue]blue!")
+ console.print("[yellow]yellow!")
+ console.print("[bold yellow]bold yellow!")
+ console.print("[bright_yellow]bright_yellow!")
+ console.print("[dim bright_yellow]dim bright_yellow!")
+ console.print("[italic cyan]italic cyan!")
+ console.print("[bold white on blue]bold white on blue!")
+ console.print("[reverse bold white on blue]reverse bold white on blue!")
+ console.print("[bold black on cyan]bold black on cyan!")
+ console.print("[black on green]black on green!")
+ console.print("[blue on green]blue on green!")
+ console.print("[white on black]white on black!")
+ console.print("[black on white]black on white!")
+ console.print("[#1BB152 on #DA812D]#1BB152 on #DA812D!")
+
+ # Check cursor movement
+ console.rule("Checking cursor movement")
+ console.print()
+ term.move_cursor_backward()
+ term.move_cursor_backward()
+ term.write_text("went back and wrapped to prev line")
+ time.sleep(1)
+ term.move_cursor_up()
+ term.write_text("we go up")
+ time.sleep(1)
+ term.move_cursor_down()
+ term.write_text("and down")
+ time.sleep(1)
+ term.move_cursor_up()
+ term.move_cursor_backward()
+ term.move_cursor_backward()
+ term.write_text("we went up and back 2")
+ time.sleep(1)
+ term.move_cursor_down()
+ term.move_cursor_backward()
+ term.move_cursor_backward()
+ term.write_text("we went down and back 2")
+ time.sleep(1)
+
+ # Check erasing of lines
+ term.hide_cursor()
+ console.print()
+ console.rule("Checking line erasing")
+ console.print("\n...Deleting to the start of the line...")
+ term.write_text("The red arrow shows the cursor location, and direction of erase")
+ time.sleep(1)
+ term.move_cursor_to_column(16)
+ term.write_styled("<", Style.parse("black on red"))
+ term.move_cursor_backward()
+ time.sleep(1)
+ term.erase_start_of_line()
+ time.sleep(1)
+
+ console.print("\n\n...And to the end of the line...")
+ term.write_text("The red arrow shows the cursor location, and direction of erase")
+ time.sleep(1)
+
+ term.move_cursor_to_column(16)
+ term.write_styled(">", Style.parse("black on red"))
+ time.sleep(1)
+ term.erase_end_of_line()
+ time.sleep(1)
+
+ console.print("\n\n...Now the whole line will be erased...")
+ term.write_styled("I'm going to disappear!", style=Style.parse("black on cyan"))
+ time.sleep(1)
+ term.erase_line()
+
+ term.show_cursor()
+ print("\n")
diff --git a/src/pip/_vendor/rich/_windows.py b/src/pip/_vendor/rich/_windows.py
new file mode 100644
index 000000000..10fc0d7e9
--- /dev/null
+++ b/src/pip/_vendor/rich/_windows.py
@@ -0,0 +1,72 @@
+import sys
+from dataclasses import dataclass
+
+
+@dataclass
+class WindowsConsoleFeatures:
+ """Windows features available."""
+
+ vt: bool = False
+ """The console supports VT codes."""
+ truecolor: bool = False
+ """The console supports truecolor."""
+
+
+try:
+ import ctypes
+ from ctypes import LibraryLoader
+
+ if sys.platform == "win32":
+ windll = LibraryLoader(ctypes.WinDLL)
+ else:
+ windll = None
+ raise ImportError("Not windows")
+
+ from pip._vendor.rich._win32_console import (
+ ENABLE_VIRTUAL_TERMINAL_PROCESSING,
+ GetConsoleMode,
+ GetStdHandle,
+ LegacyWindowsError,
+ )
+
+except (AttributeError, ImportError, ValueError):
+
+ # Fallback if we can't load the Windows DLL
+ def get_windows_console_features() -> WindowsConsoleFeatures:
+ features = WindowsConsoleFeatures()
+ return features
+
+else:
+
+ def get_windows_console_features() -> WindowsConsoleFeatures:
+ """Get windows console features.
+
+ Returns:
+ WindowsConsoleFeatures: An instance of WindowsConsoleFeatures.
+ """
+ handle = GetStdHandle()
+ try:
+ console_mode = GetConsoleMode(handle)
+ success = True
+ except LegacyWindowsError:
+ console_mode = 0
+ success = False
+ vt = bool(success and console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
+ truecolor = False
+ if vt:
+ win_version = sys.getwindowsversion()
+ truecolor = win_version.major > 10 or (
+ win_version.major == 10 and win_version.build >= 15063
+ )
+ features = WindowsConsoleFeatures(vt=vt, truecolor=truecolor)
+ return features
+
+
+if __name__ == "__main__":
+ import platform
+
+ features = get_windows_console_features()
+ from pip._vendor.rich import print
+
+ print(f'platform="{platform.system()}"')
+ print(repr(features))
diff --git a/src/pip/_vendor/rich/_windows_renderer.py b/src/pip/_vendor/rich/_windows_renderer.py
new file mode 100644
index 000000000..5ece05649
--- /dev/null
+++ b/src/pip/_vendor/rich/_windows_renderer.py
@@ -0,0 +1,56 @@
+from typing import Iterable, Sequence, Tuple, cast
+
+from pip._vendor.rich._win32_console import LegacyWindowsTerm, WindowsCoordinates
+from pip._vendor.rich.segment import ControlCode, ControlType, Segment
+
+
+def legacy_windows_render(buffer: Iterable[Segment], term: LegacyWindowsTerm) -> None:
+ """Makes appropriate Windows Console API calls based on the segments in the buffer.
+
+ Args:
+ buffer (Iterable[Segment]): Iterable of Segments to convert to Win32 API calls.
+ term (LegacyWindowsTerm): Used to call the Windows Console API.
+ """
+ for text, style, control in buffer:
+ if not control:
+ if style:
+ term.write_styled(text, style)
+ else:
+ term.write_text(text)
+ else:
+ control_codes: Sequence[ControlCode] = control
+ for control_code in control_codes:
+ control_type = control_code[0]
+ if control_type == ControlType.CURSOR_MOVE_TO:
+ _, x, y = cast(Tuple[ControlType, int, int], control_code)
+ term.move_cursor_to(WindowsCoordinates(row=y - 1, col=x - 1))
+ elif control_type == ControlType.CARRIAGE_RETURN:
+ term.write_text("\r")
+ elif control_type == ControlType.HOME:
+ term.move_cursor_to(WindowsCoordinates(0, 0))
+ elif control_type == ControlType.CURSOR_UP:
+ term.move_cursor_up()
+ elif control_type == ControlType.CURSOR_DOWN:
+ term.move_cursor_down()
+ elif control_type == ControlType.CURSOR_FORWARD:
+ term.move_cursor_forward()
+ elif control_type == ControlType.CURSOR_BACKWARD:
+ term.move_cursor_backward()
+ elif control_type == ControlType.CURSOR_MOVE_TO_COLUMN:
+ _, column = cast(Tuple[ControlType, int], control_code)
+ term.move_cursor_to_column(column - 1)
+ elif control_type == ControlType.HIDE_CURSOR:
+ term.hide_cursor()
+ elif control_type == ControlType.SHOW_CURSOR:
+ term.show_cursor()
+ elif control_type == ControlType.ERASE_IN_LINE:
+ _, mode = cast(Tuple[ControlType, int], control_code)
+ if mode == 0:
+ term.erase_end_of_line()
+ elif mode == 1:
+ term.erase_start_of_line()
+ elif mode == 2:
+ term.erase_line()
+ elif control_type == ControlType.SET_WINDOW_TITLE:
+ _, title = cast(Tuple[ControlType, str], control_code)
+ term.set_title(title)
diff --git a/src/pip/_vendor/rich/_wrap.py b/src/pip/_vendor/rich/_wrap.py
new file mode 100644
index 000000000..c45f193f7
--- /dev/null
+++ b/src/pip/_vendor/rich/_wrap.py
@@ -0,0 +1,56 @@
+import re
+from typing import Iterable, List, Tuple
+
+from ._loop import loop_last
+from .cells import cell_len, chop_cells
+
+re_word = re.compile(r"\s*\S+\s*")
+
+
+def words(text: str) -> Iterable[Tuple[int, int, str]]:
+ position = 0
+ word_match = re_word.match(text, position)
+ while word_match is not None:
+ start, end = word_match.span()
+ word = word_match.group(0)
+ yield start, end, word
+ word_match = re_word.match(text, end)
+
+
+def divide_line(text: str, width: int, fold: bool = True) -> List[int]:
+ divides: List[int] = []
+ append = divides.append
+ line_position = 0
+ _cell_len = cell_len
+ for start, _end, word in words(text):
+ word_length = _cell_len(word.rstrip())
+ if line_position + word_length > width:
+ if word_length > width:
+ if fold:
+ chopped_words = chop_cells(word, max_size=width, position=0)
+ for last, line in loop_last(chopped_words):
+ if start:
+ append(start)
+
+ if last:
+ line_position = _cell_len(line)
+ else:
+ start += len(line)
+ else:
+ if start:
+ append(start)
+ line_position = _cell_len(word)
+ elif line_position and start:
+ append(start)
+ line_position = _cell_len(word)
+ else:
+ line_position += _cell_len(word)
+ return divides
+
+
+if __name__ == "__main__": # pragma: no cover
+ from .console import Console
+
+ console = Console(width=10)
+ console.print("12345 abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ 12345")
+ print(chop_cells("abcdefghijklmnopqrstuvwxyz", 10, position=2))
diff --git a/src/pip/_vendor/rich/abc.py b/src/pip/_vendor/rich/abc.py
new file mode 100644
index 000000000..e6e498efa
--- /dev/null
+++ b/src/pip/_vendor/rich/abc.py
@@ -0,0 +1,33 @@
+from abc import ABC
+
+
+class RichRenderable(ABC):
+ """An abstract base class for Rich renderables.
+
+ Note that there is no need to extend this class, the intended use is to check if an
+ object supports the Rich renderable protocol. For example::
+
+ if isinstance(my_object, RichRenderable):
+ console.print(my_object)
+
+ """
+
+ @classmethod
+ def __subclasshook__(cls, other: type) -> bool:
+ """Check if this class supports the rich render protocol."""
+ return hasattr(other, "__rich_console__") or hasattr(other, "__rich__")
+
+
+if __name__ == "__main__": # pragma: no cover
+ from pip._vendor.rich.text import Text
+
+ t = Text()
+ print(isinstance(Text, RichRenderable))
+ print(isinstance(t, RichRenderable))
+
+ class Foo:
+ pass
+
+ f = Foo()
+ print(isinstance(f, RichRenderable))
+ print(isinstance("", RichRenderable))
diff --git a/src/pip/_vendor/rich/align.py b/src/pip/_vendor/rich/align.py
new file mode 100644
index 000000000..d5abb5947
--- /dev/null
+++ b/src/pip/_vendor/rich/align.py
@@ -0,0 +1,311 @@
+import sys
+from itertools import chain
+from typing import TYPE_CHECKING, Iterable, Optional
+
+if sys.version_info >= (3, 8):
+ from typing import Literal
+else:
+ from pip._vendor.typing_extensions import Literal # pragma: no cover
+
+from .constrain import Constrain
+from .jupyter import JupyterMixin
+from .measure import Measurement
+from .segment import Segment
+from .style import StyleType
+
+if TYPE_CHECKING:
+ from .console import Console, ConsoleOptions, RenderableType, RenderResult
+
+AlignMethod = Literal["left", "center", "right"]
+VerticalAlignMethod = Literal["top", "middle", "bottom"]
+
+
+class Align(JupyterMixin):
+ """Align a renderable by adding spaces if necessary.
+
+ Args:
+ renderable (RenderableType): A console renderable.
+ align (AlignMethod): One of "left", "center", or "right""
+ style (StyleType, optional): An optional style to apply to the background.
+ vertical (Optional[VerticalAlginMethod], optional): Optional vertical align, one of "top", "middle", or "bottom". Defaults to None.
+ pad (bool, optional): Pad the right with spaces. Defaults to True.
+ width (int, optional): Restrict contents to given width, or None to use default width. Defaults to None.
+ height (int, optional): Set height of align renderable, or None to fit to contents. Defaults to None.
+
+ Raises:
+ ValueError: if ``align`` is not one of the expected values.
+ """
+
+ def __init__(
+ self,
+ renderable: "RenderableType",
+ align: AlignMethod = "left",
+ style: Optional[StyleType] = None,
+ *,
+ vertical: Optional[VerticalAlignMethod] = None,
+ pad: bool = True,
+ width: Optional[int] = None,
+ height: Optional[int] = None,
+ ) -> None:
+ if align not in ("left", "center", "right"):
+ raise ValueError(
+ f'invalid value for align, expected "left", "center", or "right" (not {align!r})'
+ )
+ if vertical is not None and vertical not in ("top", "middle", "bottom"):
+ raise ValueError(
+ f'invalid value for vertical, expected "top", "middle", or "bottom" (not {vertical!r})'
+ )
+ self.renderable = renderable
+ self.align = align
+ self.style = style
+ self.vertical = vertical
+ self.pad = pad
+ self.width = width
+ self.height = height
+
+ def __repr__(self) -> str:
+ return f"Align({self.renderable!r}, {self.align!r})"
+
+ @classmethod
+ def left(
+ cls,
+ renderable: "RenderableType",
+ style: Optional[StyleType] = None,
+ *,
+ vertical: Optional[VerticalAlignMethod] = None,
+ pad: bool = True,
+ width: Optional[int] = None,
+ height: Optional[int] = None,
+ ) -> "Align":
+ """Align a renderable to the left."""
+ return cls(
+ renderable,
+ "left",
+ style=style,
+ vertical=vertical,
+ pad=pad,
+ width=width,
+ height=height,
+ )
+
+ @classmethod
+ def center(
+ cls,
+ renderable: "RenderableType",
+ style: Optional[StyleType] = None,
+ *,
+ vertical: Optional[VerticalAlignMethod] = None,
+ pad: bool = True,
+ width: Optional[int] = None,
+ height: Optional[int] = None,
+ ) -> "Align":
+ """Align a renderable to the center."""
+ return cls(
+ renderable,
+ "center",
+ style=style,
+ vertical=vertical,
+ pad=pad,
+ width=width,
+ height=height,
+ )
+
+ @classmethod
+ def right(
+ cls,
+ renderable: "RenderableType",
+ style: Optional[StyleType] = None,
+ *,
+ vertical: Optional[VerticalAlignMethod] = None,
+ pad: bool = True,
+ width: Optional[int] = None,
+ height: Optional[int] = None,
+ ) -> "Align":
+ """Align a renderable to the right."""
+ return cls(
+ renderable,
+ "right",
+ style=style,
+ vertical=vertical,
+ pad=pad,
+ width=width,
+ height=height,
+ )
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ align = self.align
+ width = console.measure(self.renderable, options=options).maximum
+ rendered = console.render(
+ Constrain(
+ self.renderable, width if self.width is None else min(width, self.width)
+ ),
+ options.update(height=None),
+ )
+ lines = list(Segment.split_lines(rendered))
+ width, height = Segment.get_shape(lines)
+ lines = Segment.set_shape(lines, width, height)
+ new_line = Segment.line()
+ excess_space = options.max_width - width
+ style = console.get_style(self.style) if self.style is not None else None
+
+ def generate_segments() -> Iterable[Segment]:
+ if excess_space <= 0:
+ # Exact fit
+ for line in lines:
+ yield from line
+ yield new_line
+
+ elif align == "left":
+ # Pad on the right
+ pad = Segment(" " * excess_space, style) if self.pad else None
+ for line in lines:
+ yield from line
+ if pad:
+ yield pad
+ yield new_line
+
+ elif align == "center":
+ # Pad left and right
+ left = excess_space // 2
+ pad = Segment(" " * left, style)
+ pad_right = (
+ Segment(" " * (excess_space - left), style) if self.pad else None
+ )
+ for line in lines:
+ if left:
+ yield pad
+ yield from line
+ if pad_right:
+ yield pad_right
+ yield new_line
+
+ elif align == "right":
+ # Padding on left
+ pad = Segment(" " * excess_space, style)
+ for line in lines:
+ yield pad
+ yield from line
+ yield new_line
+
+ blank_line = (
+ Segment(f"{' ' * (self.width or options.max_width)}\n", style)
+ if self.pad
+ else Segment("\n")
+ )
+
+ def blank_lines(count: int) -> Iterable[Segment]:
+ if count > 0:
+ for _ in range(count):
+ yield blank_line
+
+ vertical_height = self.height or options.height
+ iter_segments: Iterable[Segment]
+ if self.vertical and vertical_height is not None:
+ if self.vertical == "top":
+ bottom_space = vertical_height - height
+ iter_segments = chain(generate_segments(), blank_lines(bottom_space))
+ elif self.vertical == "middle":
+ top_space = (vertical_height - height) // 2
+ bottom_space = vertical_height - top_space - height
+ iter_segments = chain(
+ blank_lines(top_space),
+ generate_segments(),
+ blank_lines(bottom_space),
+ )
+ else: # self.vertical == "bottom":
+ top_space = vertical_height - height
+ iter_segments = chain(blank_lines(top_space), generate_segments())
+ else:
+ iter_segments = generate_segments()
+ if self.style:
+ style = console.get_style(self.style)
+ iter_segments = Segment.apply_style(iter_segments, style)
+ yield from iter_segments
+
+ def __rich_measure__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> Measurement:
+ measurement = Measurement.get(console, options, self.renderable)
+ return measurement
+
+
+class VerticalCenter(JupyterMixin):
+ """Vertically aligns a renderable.
+
+ Warn:
+ This class is deprecated and may be removed in a future version. Use Align class with
+ `vertical="middle"`.
+
+ Args:
+ renderable (RenderableType): A renderable object.
+ """
+
+ def __init__(
+ self,
+ renderable: "RenderableType",
+ style: Optional[StyleType] = None,
+ ) -> None:
+ self.renderable = renderable
+ self.style = style
+
+ def __repr__(self) -> str:
+ return f"VerticalCenter({self.renderable!r})"
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ style = console.get_style(self.style) if self.style is not None else None
+ lines = console.render_lines(
+ self.renderable, options.update(height=None), pad=False
+ )
+ width, _height = Segment.get_shape(lines)
+ new_line = Segment.line()
+ height = options.height or options.size.height
+ top_space = (height - len(lines)) // 2
+ bottom_space = height - top_space - len(lines)
+ blank_line = Segment(f"{' ' * width}", style)
+
+ def blank_lines(count: int) -> Iterable[Segment]:
+ for _ in range(count):
+ yield blank_line
+ yield new_line
+
+ if top_space > 0:
+ yield from blank_lines(top_space)
+ for line in lines:
+ yield from line
+ yield new_line
+ if bottom_space > 0:
+ yield from blank_lines(bottom_space)
+
+ def __rich_measure__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> Measurement:
+ measurement = Measurement.get(console, options, self.renderable)
+ return measurement
+
+
+if __name__ == "__main__": # pragma: no cover
+ from pip._vendor.rich.console import Console, Group
+ from pip._vendor.rich.highlighter import ReprHighlighter
+ from pip._vendor.rich.panel import Panel
+
+ highlighter = ReprHighlighter()
+ console = Console()
+
+ panel = Panel(
+ Group(
+ Align.left(highlighter("align='left'")),
+ Align.center(highlighter("align='center'")),
+ Align.right(highlighter("align='right'")),
+ ),
+ width=60,
+ style="on dark_blue",
+ title="Algin",
+ )
+
+ console.print(
+ Align.center(panel, vertical="middle", style="on red", height=console.height)
+ )
diff --git a/src/pip/_vendor/rich/ansi.py b/src/pip/_vendor/rich/ansi.py
new file mode 100644
index 000000000..92ef51941
--- /dev/null
+++ b/src/pip/_vendor/rich/ansi.py
@@ -0,0 +1,237 @@
+import re
+import sys
+from contextlib import suppress
+from typing import Iterable, NamedTuple, Optional
+
+from .color import Color
+from .style import Style
+from .text import Text
+
+re_ansi = re.compile(
+ r"""
+(?:\x1b\](.*?)\x1b\\)|
+(?:\x1b([(@-Z\\-_]|\[[0-?]*[ -/]*[@-~]))
+""",
+ re.VERBOSE,
+)
+
+
+class _AnsiToken(NamedTuple):
+ """Result of ansi tokenized string."""
+
+ plain: str = ""
+ sgr: Optional[str] = ""
+ osc: Optional[str] = ""
+
+
+def _ansi_tokenize(ansi_text: str) -> Iterable[_AnsiToken]:
+ """Tokenize a string in to plain text and ANSI codes.
+
+ Args:
+ ansi_text (str): A String containing ANSI codes.
+
+ Yields:
+ AnsiToken: A named tuple of (plain, sgr, osc)
+ """
+
+ position = 0
+ sgr: Optional[str]
+ osc: Optional[str]
+ for match in re_ansi.finditer(ansi_text):
+ start, end = match.span(0)
+ osc, sgr = match.groups()
+ if start > position:
+ yield _AnsiToken(ansi_text[position:start])
+ if sgr:
+ if sgr.endswith("m"):
+ yield _AnsiToken("", sgr[1:-1], osc)
+ else:
+ yield _AnsiToken("", sgr, osc)
+ position = end
+ if position < len(ansi_text):
+ yield _AnsiToken(ansi_text[position:])
+
+
+SGR_STYLE_MAP = {
+ 1: "bold",
+ 2: "dim",
+ 3: "italic",
+ 4: "underline",
+ 5: "blink",
+ 6: "blink2",
+ 7: "reverse",
+ 8: "conceal",
+ 9: "strike",
+ 21: "underline2",
+ 22: "not dim not bold",
+ 23: "not italic",
+ 24: "not underline",
+ 25: "not blink",
+ 26: "not blink2",
+ 27: "not reverse",
+ 28: "not conceal",
+ 29: "not strike",
+ 30: "color(0)",
+ 31: "color(1)",
+ 32: "color(2)",
+ 33: "color(3)",
+ 34: "color(4)",
+ 35: "color(5)",
+ 36: "color(6)",
+ 37: "color(7)",
+ 39: "default",
+ 40: "on color(0)",
+ 41: "on color(1)",
+ 42: "on color(2)",
+ 43: "on color(3)",
+ 44: "on color(4)",
+ 45: "on color(5)",
+ 46: "on color(6)",
+ 47: "on color(7)",
+ 49: "on default",
+ 51: "frame",
+ 52: "encircle",
+ 53: "overline",
+ 54: "not frame not encircle",
+ 55: "not overline",
+ 90: "color(8)",
+ 91: "color(9)",
+ 92: "color(10)",
+ 93: "color(11)",
+ 94: "color(12)",
+ 95: "color(13)",
+ 96: "color(14)",
+ 97: "color(15)",
+ 100: "on color(8)",
+ 101: "on color(9)",
+ 102: "on color(10)",
+ 103: "on color(11)",
+ 104: "on color(12)",
+ 105: "on color(13)",
+ 106: "on color(14)",
+ 107: "on color(15)",
+}
+
+
+class AnsiDecoder:
+ """Translate ANSI code in to styled Text."""
+
+ def __init__(self) -> None:
+ self.style = Style.null()
+
+ def decode(self, terminal_text: str) -> Iterable[Text]:
+ """Decode ANSI codes in an iterable of lines.
+
+ Args:
+ lines (Iterable[str]): An iterable of lines of terminal output.
+
+ Yields:
+ Text: Marked up Text.
+ """
+ for line in terminal_text.splitlines():
+ yield self.decode_line(line)
+
+ def decode_line(self, line: str) -> Text:
+ """Decode a line containing ansi codes.
+
+ Args:
+ line (str): A line of terminal output.
+
+ Returns:
+ Text: A Text instance marked up according to ansi codes.
+ """
+ from_ansi = Color.from_ansi
+ from_rgb = Color.from_rgb
+ _Style = Style
+ text = Text()
+ append = text.append
+ line = line.rsplit("\r", 1)[-1]
+ for plain_text, sgr, osc in _ansi_tokenize(line):
+ if plain_text:
+ append(plain_text, self.style or None)
+ elif osc is not None:
+ if osc.startswith("8;"):
+ _params, semicolon, link = osc[2:].partition(";")
+ if semicolon:
+ self.style = self.style.update_link(link or None)
+ elif sgr is not None:
+ # Translate in to semi-colon separated codes
+ # Ignore invalid codes, because we want to be lenient
+ codes = [
+ min(255, int(_code) if _code else 0)
+ for _code in sgr.split(";")
+ if _code.isdigit() or _code == ""
+ ]
+ iter_codes = iter(codes)
+ for code in iter_codes:
+ if code == 0:
+ # reset
+ self.style = _Style.null()
+ elif code in SGR_STYLE_MAP:
+ # styles
+ self.style += _Style.parse(SGR_STYLE_MAP[code])
+ elif code == 38:
+ #  Foreground
+ with suppress(StopIteration):
+ color_type = next(iter_codes)
+ if color_type == 5:
+ self.style += _Style.from_color(
+ from_ansi(next(iter_codes))
+ )
+ elif color_type == 2:
+ self.style += _Style.from_color(
+ from_rgb(
+ next(iter_codes),
+ next(iter_codes),
+ next(iter_codes),
+ )
+ )
+ elif code == 48:
+ # Background
+ with suppress(StopIteration):
+ color_type = next(iter_codes)
+ if color_type == 5:
+ self.style += _Style.from_color(
+ None, from_ansi(next(iter_codes))
+ )
+ elif color_type == 2:
+ self.style += _Style.from_color(
+ None,
+ from_rgb(
+ next(iter_codes),
+ next(iter_codes),
+ next(iter_codes),
+ ),
+ )
+
+ return text
+
+
+if sys.platform != "win32" and __name__ == "__main__": # pragma: no cover
+ import io
+ import os
+ import pty
+ import sys
+
+ decoder = AnsiDecoder()
+
+ stdout = io.BytesIO()
+
+ def read(fd: int) -> bytes:
+ data = os.read(fd, 1024)
+ stdout.write(data)
+ return data
+
+ pty.spawn(sys.argv[1:], read)
+
+ from .console import Console
+
+ console = Console(record=True)
+
+ stdout_result = stdout.getvalue().decode("utf-8")
+ print(stdout_result)
+
+ for line in decoder.decode(stdout_result):
+ console.print(line)
+
+ console.save_html("stdout.html")
diff --git a/src/pip/_vendor/rich/bar.py b/src/pip/_vendor/rich/bar.py
new file mode 100644
index 000000000..ed86a552d
--- /dev/null
+++ b/src/pip/_vendor/rich/bar.py
@@ -0,0 +1,94 @@
+from typing import Optional, Union
+
+from .color import Color
+from .console import Console, ConsoleOptions, RenderResult
+from .jupyter import JupyterMixin
+from .measure import Measurement
+from .segment import Segment
+from .style import Style
+
+# There are left-aligned characters for 1/8 to 7/8, but
+# the right-aligned characters exist only for 1/8 and 4/8.
+BEGIN_BLOCK_ELEMENTS = ["█", "█", "█", "▐", "▐", "▐", "▕", "▕"]
+END_BLOCK_ELEMENTS = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"]
+FULL_BLOCK = "█"
+
+
+class Bar(JupyterMixin):
+ """Renders a solid block bar.
+
+ Args:
+ size (float): Value for the end of the bar.
+ begin (float): Begin point (between 0 and size, inclusive).
+ end (float): End point (between 0 and size, inclusive).
+ width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None.
+ color (Union[Color, str], optional): Color of the bar. Defaults to "default".
+ bgcolor (Union[Color, str], optional): Color of bar background. Defaults to "default".
+ """
+
+ def __init__(
+ self,
+ size: float,
+ begin: float,
+ end: float,
+ *,
+ width: Optional[int] = None,
+ color: Union[Color, str] = "default",
+ bgcolor: Union[Color, str] = "default",
+ ):
+ self.size = size
+ self.begin = max(begin, 0)
+ self.end = min(end, size)
+ self.width = width
+ self.style = Style(color=color, bgcolor=bgcolor)
+
+ def __repr__(self) -> str:
+ return f"Bar({self.size}, {self.begin}, {self.end})"
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+
+ width = min(
+ self.width if self.width is not None else options.max_width,
+ options.max_width,
+ )
+
+ if self.begin >= self.end:
+ yield Segment(" " * width, self.style)
+ yield Segment.line()
+ return
+
+ prefix_complete_eights = int(width * 8 * self.begin / self.size)
+ prefix_bar_count = prefix_complete_eights // 8
+ prefix_eights_count = prefix_complete_eights % 8
+
+ body_complete_eights = int(width * 8 * self.end / self.size)
+ body_bar_count = body_complete_eights // 8
+ body_eights_count = body_complete_eights % 8
+
+ # When start and end fall into the same cell, we ideally should render
+ # a symbol that's "center-aligned", but there is no good symbol in Unicode.
+ # In this case, we fall back to right-aligned block symbol for simplicity.
+
+ prefix = " " * prefix_bar_count
+ if prefix_eights_count:
+ prefix += BEGIN_BLOCK_ELEMENTS[prefix_eights_count]
+
+ body = FULL_BLOCK * body_bar_count
+ if body_eights_count:
+ body += END_BLOCK_ELEMENTS[body_eights_count]
+
+ suffix = " " * (width - len(body))
+
+ yield Segment(prefix + body[len(prefix) :] + suffix, self.style)
+ yield Segment.line()
+
+ def __rich_measure__(
+ self, console: Console, options: ConsoleOptions
+ ) -> Measurement:
+ return (
+ Measurement(self.width, self.width)
+ if self.width is not None
+ else Measurement(4, options.max_width)
+ )
diff --git a/src/pip/_vendor/rich/box.py b/src/pip/_vendor/rich/box.py
new file mode 100644
index 000000000..97d2a9444
--- /dev/null
+++ b/src/pip/_vendor/rich/box.py
@@ -0,0 +1,517 @@
+import sys
+from typing import TYPE_CHECKING, Iterable, List
+
+if sys.version_info >= (3, 8):
+ from typing import Literal
+else:
+ from pip._vendor.typing_extensions import Literal # pragma: no cover
+
+
+from ._loop import loop_last
+
+if TYPE_CHECKING:
+ from pip._vendor.rich.console import ConsoleOptions
+
+
+class Box:
+ """Defines characters to render boxes.
+
+ ┌─┬┐ top
+ │ ││ head
+ ├─┼┤ head_row
+ │ ││ mid
+ ├─┼┤ row
+ ├─┼┤ foot_row
+ │ ││ foot
+ └─┴┘ bottom
+
+ Args:
+ box (str): Characters making up box.
+ ascii (bool, optional): True if this box uses ascii characters only. Default is False.
+ """
+
+ def __init__(self, box: str, *, ascii: bool = False) -> None:
+ self._box = box
+ self.ascii = ascii
+ line1, line2, line3, line4, line5, line6, line7, line8 = box.splitlines()
+ # top
+ self.top_left, self.top, self.top_divider, self.top_right = iter(line1)
+ # head
+ self.head_left, _, self.head_vertical, self.head_right = iter(line2)
+ # head_row
+ (
+ self.head_row_left,
+ self.head_row_horizontal,
+ self.head_row_cross,
+ self.head_row_right,
+ ) = iter(line3)
+
+ # mid
+ self.mid_left, _, self.mid_vertical, self.mid_right = iter(line4)
+ # row
+ self.row_left, self.row_horizontal, self.row_cross, self.row_right = iter(line5)
+ # foot_row
+ (
+ self.foot_row_left,
+ self.foot_row_horizontal,
+ self.foot_row_cross,
+ self.foot_row_right,
+ ) = iter(line6)
+ # foot
+ self.foot_left, _, self.foot_vertical, self.foot_right = iter(line7)
+ # bottom
+ self.bottom_left, self.bottom, self.bottom_divider, self.bottom_right = iter(
+ line8
+ )
+
+ def __repr__(self) -> str:
+ return "Box(...)"
+
+ def __str__(self) -> str:
+ return self._box
+
+ def substitute(self, options: "ConsoleOptions", safe: bool = True) -> "Box":
+ """Substitute this box for another if it won't render due to platform issues.
+
+ Args:
+ options (ConsoleOptions): Console options used in rendering.
+ safe (bool, optional): Substitute this for another Box if there are known problems
+ displaying on the platform (currently only relevant on Windows). Default is True.
+
+ Returns:
+ Box: A different Box or the same Box.
+ """
+ box = self
+ if options.legacy_windows and safe:
+ box = LEGACY_WINDOWS_SUBSTITUTIONS.get(box, box)
+ if options.ascii_only and not box.ascii:
+ box = ASCII
+ return box
+
+ def get_plain_headed_box(self) -> "Box":
+ """If this box uses special characters for the borders of the header, then
+ return the equivalent box that does not.
+
+ Returns:
+ Box: The most similar Box that doesn't use header-specific box characters.
+ If the current Box already satisfies this criterion, then it's returned.
+ """
+ return PLAIN_HEADED_SUBSTITUTIONS.get(self, self)
+
+ def get_top(self, widths: Iterable[int]) -> str:
+ """Get the top of a simple box.
+
+ Args:
+ widths (List[int]): Widths of columns.
+
+ Returns:
+ str: A string of box characters.
+ """
+
+ parts: List[str] = []
+ append = parts.append
+ append(self.top_left)
+ for last, width in loop_last(widths):
+ append(self.top * width)
+ if not last:
+ append(self.top_divider)
+ append(self.top_right)
+ return "".join(parts)
+
+ def get_row(
+ self,
+ widths: Iterable[int],
+ level: Literal["head", "row", "foot", "mid"] = "row",
+ edge: bool = True,
+ ) -> str:
+ """Get the top of a simple box.
+
+ Args:
+ width (List[int]): Widths of columns.
+
+ Returns:
+ str: A string of box characters.
+ """
+ if level == "head":
+ left = self.head_row_left
+ horizontal = self.head_row_horizontal
+ cross = self.head_row_cross
+ right = self.head_row_right
+ elif level == "row":
+ left = self.row_left
+ horizontal = self.row_horizontal
+ cross = self.row_cross
+ right = self.row_right
+ elif level == "mid":
+ left = self.mid_left
+ horizontal = " "
+ cross = self.mid_vertical
+ right = self.mid_right
+ elif level == "foot":
+ left = self.foot_row_left
+ horizontal = self.foot_row_horizontal
+ cross = self.foot_row_cross
+ right = self.foot_row_right
+ else:
+ raise ValueError("level must be 'head', 'row' or 'foot'")
+
+ parts: List[str] = []
+ append = parts.append
+ if edge:
+ append(left)
+ for last, width in loop_last(widths):
+ append(horizontal * width)
+ if not last:
+ append(cross)
+ if edge:
+ append(right)
+ return "".join(parts)
+
+ def get_bottom(self, widths: Iterable[int]) -> str:
+ """Get the bottom of a simple box.
+
+ Args:
+ widths (List[int]): Widths of columns.
+
+ Returns:
+ str: A string of box characters.
+ """
+
+ parts: List[str] = []
+ append = parts.append
+ append(self.bottom_left)
+ for last, width in loop_last(widths):
+ append(self.bottom * width)
+ if not last:
+ append(self.bottom_divider)
+ append(self.bottom_right)
+ return "".join(parts)
+
+
+ASCII: Box = Box(
+ """\
++--+
+| ||
+|-+|
+| ||
+|-+|
+|-+|
+| ||
++--+
+""",
+ ascii=True,
+)
+
+ASCII2: Box = Box(
+ """\
++-++
+| ||
++-++
+| ||
++-++
++-++
+| ||
++-++
+""",
+ ascii=True,
+)
+
+ASCII_DOUBLE_HEAD: Box = Box(
+ """\
++-++
+| ||
++=++
+| ||
++-++
++-++
+| ||
++-++
+""",
+ ascii=True,
+)
+
+SQUARE: Box = Box(
+ """\
+┌─┬┐
+│ ││
+├─┼┤
+│ ││
+├─┼┤
+├─┼┤
+│ ││
+└─┴┘
+"""
+)
+
+SQUARE_DOUBLE_HEAD: Box = Box(
+ """\
+┌─┬┐
+│ ││
+╞═╪╡
+│ ││
+├─┼┤
+├─┼┤
+│ ││
+└─┴┘
+"""
+)
+
+MINIMAL: Box = Box(
+ """\
+ ╷
+ │
+╶─┼╴
+ │
+╶─┼╴
+╶─┼╴
+ │
+ ╵
+"""
+)
+
+
+MINIMAL_HEAVY_HEAD: Box = Box(
+ """\
+ ╷
+ │
+╺━┿╸
+ │
+╶─┼╴
+╶─┼╴
+ │
+ ╵
+"""
+)
+
+MINIMAL_DOUBLE_HEAD: Box = Box(
+ """\
+ ╷
+ │
+ ═╪
+ │
+ ─┼
+ ─┼
+ │
+ ╵
+"""
+)
+
+
+SIMPLE: Box = Box(
+ """\
+
+
+ ──
+
+
+ ──
+
+
+"""
+)
+
+SIMPLE_HEAD: Box = Box(
+ """\
+
+
+ ──
+
+
+
+
+
+"""
+)
+
+
+SIMPLE_HEAVY: Box = Box(
+ """\
+
+
+ ━━
+
+
+ ━━
+
+
+"""
+)
+
+
+HORIZONTALS: Box = Box(
+ """\
+ ──
+
+ ──
+
+ ──
+ ──
+
+ ──
+"""
+)
+
+ROUNDED: Box = Box(
+ """\
+╭─┬╮
+│ ││
+├─┼┤
+│ ││
+├─┼┤
+├─┼┤
+│ ││
+╰─┴╯
+"""
+)
+
+HEAVY: Box = Box(
+ """\
+┏━┳┓
+┃ ┃┃
+┣━╋┫
+┃ ┃┃
+┣━╋┫
+┣━╋┫
+┃ ┃┃
+┗━┻┛
+"""
+)
+
+HEAVY_EDGE: Box = Box(
+ """\
+┏━┯┓
+┃ │┃
+┠─┼┨
+┃ │┃
+┠─┼┨
+┠─┼┨
+┃ │┃
+┗━┷┛
+"""
+)
+
+HEAVY_HEAD: Box = Box(
+ """\
+┏━┳┓
+┃ ┃┃
+┡━╇┩
+│ ││
+├─┼┤
+├─┼┤
+│ ││
+└─┴┘
+"""
+)
+
+DOUBLE: Box = Box(
+ """\
+╔═╦╗
+║ ║║
+╠═╬╣
+║ ║║
+╠═╬╣
+╠═╬╣
+║ ║║
+╚═╩╝
+"""
+)
+
+DOUBLE_EDGE: Box = Box(
+ """\
+╔═╤╗
+║ │║
+╟─┼╢
+║ │║
+╟─┼╢
+╟─┼╢
+║ │║
+╚═╧╝
+"""
+)
+
+MARKDOWN: Box = Box(
+ """\
+
+| ||
+|-||
+| ||
+|-||
+|-||
+| ||
+
+""",
+ ascii=True,
+)
+
+# Map Boxes that don't render with raster fonts on to equivalent that do
+LEGACY_WINDOWS_SUBSTITUTIONS = {
+ ROUNDED: SQUARE,
+ MINIMAL_HEAVY_HEAD: MINIMAL,
+ SIMPLE_HEAVY: SIMPLE,
+ HEAVY: SQUARE,
+ HEAVY_EDGE: SQUARE,
+ HEAVY_HEAD: SQUARE,
+}
+
+# Map headed boxes to their headerless equivalents
+PLAIN_HEADED_SUBSTITUTIONS = {
+ HEAVY_HEAD: SQUARE,
+ SQUARE_DOUBLE_HEAD: SQUARE,
+ MINIMAL_DOUBLE_HEAD: MINIMAL,
+ MINIMAL_HEAVY_HEAD: MINIMAL,
+ ASCII_DOUBLE_HEAD: ASCII2,
+}
+
+
+if __name__ == "__main__": # pragma: no cover
+
+ from pip._vendor.rich.columns import Columns
+ from pip._vendor.rich.panel import Panel
+
+ from . import box as box
+ from .console import Console
+ from .table import Table
+ from .text import Text
+
+ console = Console(record=True)
+
+ BOXES = [
+ "ASCII",
+ "ASCII2",
+ "ASCII_DOUBLE_HEAD",
+ "SQUARE",
+ "SQUARE_DOUBLE_HEAD",
+ "MINIMAL",
+ "MINIMAL_HEAVY_HEAD",
+ "MINIMAL_DOUBLE_HEAD",
+ "SIMPLE",
+ "SIMPLE_HEAD",
+ "SIMPLE_HEAVY",
+ "HORIZONTALS",
+ "ROUNDED",
+ "HEAVY",
+ "HEAVY_EDGE",
+ "HEAVY_HEAD",
+ "DOUBLE",
+ "DOUBLE_EDGE",
+ "MARKDOWN",
+ ]
+
+ console.print(Panel("[bold green]Box Constants", style="green"), justify="center")
+ console.print()
+
+ columns = Columns(expand=True, padding=2)
+ for box_name in sorted(BOXES):
+ table = Table(
+ show_footer=True, style="dim", border_style="not dim", expand=True
+ )
+ table.add_column("Header 1", "Footer 1")
+ table.add_column("Header 2", "Footer 2")
+ table.add_row("Cell", "Cell")
+ table.add_row("Cell", "Cell")
+ table.box = getattr(box, box_name)
+ table.title = Text(f"box.{box_name}", style="magenta")
+ columns.add_renderable(table)
+ console.print(columns)
+
+ # console.save_svg("box.svg")
diff --git a/src/pip/_vendor/rich/cells.py b/src/pip/_vendor/rich/cells.py
new file mode 100644
index 000000000..139b949f7
--- /dev/null
+++ b/src/pip/_vendor/rich/cells.py
@@ -0,0 +1,154 @@
+import re
+from functools import lru_cache
+from typing import Callable, List
+
+from ._cell_widths import CELL_WIDTHS
+
+# Regex to match sequence of the most common character ranges
+_is_single_cell_widths = re.compile("^[\u0020-\u006f\u00a0\u02ff\u0370-\u0482]*$").match
+
+
+@lru_cache(4096)
+def cached_cell_len(text: str) -> int:
+ """Get the number of cells required to display text.
+
+ This method always caches, which may use up a lot of memory. It is recommended to use
+ `cell_len` over this method.
+
+ Args:
+ text (str): Text to display.
+
+ Returns:
+ int: Get the number of cells required to display text.
+ """
+ _get_size = get_character_cell_size
+ total_size = sum(_get_size(character) for character in text)
+ return total_size
+
+
+def cell_len(text: str, _cell_len: Callable[[str], int] = cached_cell_len) -> int:
+ """Get the number of cells required to display text.
+
+ Args:
+ text (str): Text to display.
+
+ Returns:
+ int: Get the number of cells required to display text.
+ """
+ if len(text) < 512:
+ return _cell_len(text)
+ _get_size = get_character_cell_size
+ total_size = sum(_get_size(character) for character in text)
+ return total_size
+
+
+@lru_cache(maxsize=4096)
+def get_character_cell_size(character: str) -> int:
+ """Get the cell size of a character.
+
+ Args:
+ character (str): A single character.
+
+ Returns:
+ int: Number of cells (0, 1 or 2) occupied by that character.
+ """
+ return _get_codepoint_cell_size(ord(character))
+
+
+@lru_cache(maxsize=4096)
+def _get_codepoint_cell_size(codepoint: int) -> int:
+ """Get the cell size of a character.
+
+ Args:
+ character (str): A single character.
+
+ Returns:
+ int: Number of cells (0, 1 or 2) occupied by that character.
+ """
+
+ _table = CELL_WIDTHS
+ lower_bound = 0
+ upper_bound = len(_table) - 1
+ index = (lower_bound + upper_bound) // 2
+ while True:
+ start, end, width = _table[index]
+ if codepoint < start:
+ upper_bound = index - 1
+ elif codepoint > end:
+ lower_bound = index + 1
+ else:
+ return 0 if width == -1 else width
+ if upper_bound < lower_bound:
+ break
+ index = (lower_bound + upper_bound) // 2
+ return 1
+
+
+def set_cell_size(text: str, total: int) -> str:
+ """Set the length of a string to fit within given number of cells."""
+
+ if _is_single_cell_widths(text):
+ size = len(text)
+ if size < total:
+ return text + " " * (total - size)
+ return text[:total]
+
+ if total <= 0:
+ return ""
+ cell_size = cell_len(text)
+ if cell_size == total:
+ return text
+ if cell_size < total:
+ return text + " " * (total - cell_size)
+
+ start = 0
+ end = len(text)
+
+ # Binary search until we find the right size
+ while True:
+ pos = (start + end) // 2
+ before = text[: pos + 1]
+ before_len = cell_len(before)
+ if before_len == total + 1 and cell_len(before[-1]) == 2:
+ return before[:-1] + " "
+ if before_len == total:
+ return before
+ if before_len > total:
+ end = pos
+ else:
+ start = pos
+
+
+# TODO: This is inefficient
+# TODO: This might not work with CWJ type characters
+def chop_cells(text: str, max_size: int, position: int = 0) -> List[str]:
+ """Break text in to equal (cell) length strings, returning the characters in reverse
+ order"""
+ _get_character_cell_size = get_character_cell_size
+ characters = [
+ (character, _get_character_cell_size(character)) for character in text
+ ]
+ total_size = position
+ lines: List[List[str]] = [[]]
+ append = lines[-1].append
+
+ for character, size in reversed(characters):
+ if total_size + size > max_size:
+ lines.append([character])
+ append = lines[-1].append
+ total_size = size
+ else:
+ total_size += size
+ append(character)
+
+ return ["".join(line) for line in lines]
+
+
+if __name__ == "__main__": # pragma: no cover
+
+ print(get_character_cell_size("😽"))
+ for line in chop_cells("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", 8):
+ print(line)
+ for n in range(80, 1, -1):
+ print(set_cell_size("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", n) + "|")
+ print("x" * n)
diff --git a/src/pip/_vendor/rich/color.py b/src/pip/_vendor/rich/color.py
new file mode 100644
index 000000000..ef2e895d7
--- /dev/null
+++ b/src/pip/_vendor/rich/color.py
@@ -0,0 +1,618 @@
+import platform
+import re
+from colorsys import rgb_to_hls
+from enum import IntEnum
+from functools import lru_cache
+from typing import TYPE_CHECKING, NamedTuple, Optional, Tuple
+
+from ._palettes import EIGHT_BIT_PALETTE, STANDARD_PALETTE, WINDOWS_PALETTE
+from .color_triplet import ColorTriplet
+from .repr import Result, rich_repr
+from .terminal_theme import DEFAULT_TERMINAL_THEME
+
+if TYPE_CHECKING: # pragma: no cover
+ from .terminal_theme import TerminalTheme
+ from .text import Text
+
+
+WINDOWS = platform.system() == "Windows"
+
+
+class ColorSystem(IntEnum):
+ """One of the 3 color system supported by terminals."""
+
+ STANDARD = 1
+ EIGHT_BIT = 2
+ TRUECOLOR = 3
+ WINDOWS = 4
+
+ def __repr__(self) -> str:
+ return f"ColorSystem.{self.name}"
+
+ def __str__(self) -> str:
+ return repr(self)
+
+
+class ColorType(IntEnum):
+ """Type of color stored in Color class."""
+
+ DEFAULT = 0
+ STANDARD = 1
+ EIGHT_BIT = 2
+ TRUECOLOR = 3
+ WINDOWS = 4
+
+ def __repr__(self) -> str:
+ return f"ColorType.{self.name}"
+
+
+ANSI_COLOR_NAMES = {
+ "black": 0,
+ "red": 1,
+ "green": 2,
+ "yellow": 3,
+ "blue": 4,
+ "magenta": 5,
+ "cyan": 6,
+ "white": 7,
+ "bright_black": 8,
+ "bright_red": 9,
+ "bright_green": 10,
+ "bright_yellow": 11,
+ "bright_blue": 12,
+ "bright_magenta": 13,
+ "bright_cyan": 14,
+ "bright_white": 15,
+ "grey0": 16,
+ "gray0": 16,
+ "navy_blue": 17,
+ "dark_blue": 18,
+ "blue3": 20,
+ "blue1": 21,
+ "dark_green": 22,
+ "deep_sky_blue4": 25,
+ "dodger_blue3": 26,
+ "dodger_blue2": 27,
+ "green4": 28,
+ "spring_green4": 29,
+ "turquoise4": 30,
+ "deep_sky_blue3": 32,
+ "dodger_blue1": 33,
+ "green3": 40,
+ "spring_green3": 41,
+ "dark_cyan": 36,
+ "light_sea_green": 37,
+ "deep_sky_blue2": 38,
+ "deep_sky_blue1": 39,
+ "spring_green2": 47,
+ "cyan3": 43,
+ "dark_turquoise": 44,
+ "turquoise2": 45,
+ "green1": 46,
+ "spring_green1": 48,
+ "medium_spring_green": 49,
+ "cyan2": 50,
+ "cyan1": 51,
+ "dark_red": 88,
+ "deep_pink4": 125,
+ "purple4": 55,
+ "purple3": 56,
+ "blue_violet": 57,
+ "orange4": 94,
+ "grey37": 59,
+ "gray37": 59,
+ "medium_purple4": 60,
+ "slate_blue3": 62,
+ "royal_blue1": 63,
+ "chartreuse4": 64,
+ "dark_sea_green4": 71,
+ "pale_turquoise4": 66,
+ "steel_blue": 67,
+ "steel_blue3": 68,
+ "cornflower_blue": 69,
+ "chartreuse3": 76,
+ "cadet_blue": 73,
+ "sky_blue3": 74,
+ "steel_blue1": 81,
+ "pale_green3": 114,
+ "sea_green3": 78,
+ "aquamarine3": 79,
+ "medium_turquoise": 80,
+ "chartreuse2": 112,
+ "sea_green2": 83,
+ "sea_green1": 85,
+ "aquamarine1": 122,
+ "dark_slate_gray2": 87,
+ "dark_magenta": 91,
+ "dark_violet": 128,
+ "purple": 129,
+ "light_pink4": 95,
+ "plum4": 96,
+ "medium_purple3": 98,
+ "slate_blue1": 99,
+ "yellow4": 106,
+ "wheat4": 101,
+ "grey53": 102,
+ "gray53": 102,
+ "light_slate_grey": 103,
+ "light_slate_gray": 103,
+ "medium_purple": 104,
+ "light_slate_blue": 105,
+ "dark_olive_green3": 149,
+ "dark_sea_green": 108,
+ "light_sky_blue3": 110,
+ "sky_blue2": 111,
+ "dark_sea_green3": 150,
+ "dark_slate_gray3": 116,
+ "sky_blue1": 117,
+ "chartreuse1": 118,
+ "light_green": 120,
+ "pale_green1": 156,
+ "dark_slate_gray1": 123,
+ "red3": 160,
+ "medium_violet_red": 126,
+ "magenta3": 164,
+ "dark_orange3": 166,
+ "indian_red": 167,
+ "hot_pink3": 168,
+ "medium_orchid3": 133,
+ "medium_orchid": 134,
+ "medium_purple2": 140,
+ "dark_goldenrod": 136,
+ "light_salmon3": 173,
+ "rosy_brown": 138,
+ "grey63": 139,
+ "gray63": 139,
+ "medium_purple1": 141,
+ "gold3": 178,
+ "dark_khaki": 143,
+ "navajo_white3": 144,
+ "grey69": 145,
+ "gray69": 145,
+ "light_steel_blue3": 146,
+ "light_steel_blue": 147,
+ "yellow3": 184,
+ "dark_sea_green2": 157,
+ "light_cyan3": 152,
+ "light_sky_blue1": 153,
+ "green_yellow": 154,
+ "dark_olive_green2": 155,
+ "dark_sea_green1": 193,
+ "pale_turquoise1": 159,
+ "deep_pink3": 162,
+ "magenta2": 200,
+ "hot_pink2": 169,
+ "orchid": 170,
+ "medium_orchid1": 207,
+ "orange3": 172,
+ "light_pink3": 174,
+ "pink3": 175,
+ "plum3": 176,
+ "violet": 177,
+ "light_goldenrod3": 179,
+ "tan": 180,
+ "misty_rose3": 181,
+ "thistle3": 182,
+ "plum2": 183,
+ "khaki3": 185,
+ "light_goldenrod2": 222,
+ "light_yellow3": 187,
+ "grey84": 188,
+ "gray84": 188,
+ "light_steel_blue1": 189,
+ "yellow2": 190,
+ "dark_olive_green1": 192,
+ "honeydew2": 194,
+ "light_cyan1": 195,
+ "red1": 196,
+ "deep_pink2": 197,
+ "deep_pink1": 199,
+ "magenta1": 201,
+ "orange_red1": 202,
+ "indian_red1": 204,
+ "hot_pink": 206,
+ "dark_orange": 208,
+ "salmon1": 209,
+ "light_coral": 210,
+ "pale_violet_red1": 211,
+ "orchid2": 212,
+ "orchid1": 213,
+ "orange1": 214,
+ "sandy_brown": 215,
+ "light_salmon1": 216,
+ "light_pink1": 217,
+ "pink1": 218,
+ "plum1": 219,
+ "gold1": 220,
+ "navajo_white1": 223,
+ "misty_rose1": 224,
+ "thistle1": 225,
+ "yellow1": 226,
+ "light_goldenrod1": 227,
+ "khaki1": 228,
+ "wheat1": 229,
+ "cornsilk1": 230,
+ "grey100": 231,
+ "gray100": 231,
+ "grey3": 232,
+ "gray3": 232,
+ "grey7": 233,
+ "gray7": 233,
+ "grey11": 234,
+ "gray11": 234,
+ "grey15": 235,
+ "gray15": 235,
+ "grey19": 236,
+ "gray19": 236,
+ "grey23": 237,
+ "gray23": 237,
+ "grey27": 238,
+ "gray27": 238,
+ "grey30": 239,
+ "gray30": 239,
+ "grey35": 240,
+ "gray35": 240,
+ "grey39": 241,
+ "gray39": 241,
+ "grey42": 242,
+ "gray42": 242,
+ "grey46": 243,
+ "gray46": 243,
+ "grey50": 244,
+ "gray50": 244,
+ "grey54": 245,
+ "gray54": 245,
+ "grey58": 246,
+ "gray58": 246,
+ "grey62": 247,
+ "gray62": 247,
+ "grey66": 248,
+ "gray66": 248,
+ "grey70": 249,
+ "gray70": 249,
+ "grey74": 250,
+ "gray74": 250,
+ "grey78": 251,
+ "gray78": 251,
+ "grey82": 252,
+ "gray82": 252,
+ "grey85": 253,
+ "gray85": 253,
+ "grey89": 254,
+ "gray89": 254,
+ "grey93": 255,
+ "gray93": 255,
+}
+
+
+class ColorParseError(Exception):
+ """The color could not be parsed."""
+
+
+RE_COLOR = re.compile(
+ r"""^
+\#([0-9a-f]{6})$|
+color\(([0-9]{1,3})\)$|
+rgb\(([\d\s,]+)\)$
+""",
+ re.VERBOSE,
+)
+
+
+@rich_repr
+class Color(NamedTuple):
+ """Terminal color definition."""
+
+ name: str
+ """The name of the color (typically the input to Color.parse)."""
+ type: ColorType
+ """The type of the color."""
+ number: Optional[int] = None
+ """The color number, if a standard color, or None."""
+ triplet: Optional[ColorTriplet] = None
+ """A triplet of color components, if an RGB color."""
+
+ def __rich__(self) -> "Text":
+ """Displays the actual color if Rich printed."""
+ from .style import Style
+ from .text import Text
+
+ return Text.assemble(
+ f"<color {self.name!r} ({self.type.name.lower()})",
+ ("⬤", Style(color=self)),
+ " >",
+ )
+
+ def __rich_repr__(self) -> Result:
+ yield self.name
+ yield self.type
+ yield "number", self.number, None
+ yield "triplet", self.triplet, None
+
+ @property
+ def system(self) -> ColorSystem:
+ """Get the native color system for this color."""
+ if self.type == ColorType.DEFAULT:
+ return ColorSystem.STANDARD
+ return ColorSystem(int(self.type))
+
+ @property
+ def is_system_defined(self) -> bool:
+ """Check if the color is ultimately defined by the system."""
+ return self.system not in (ColorSystem.EIGHT_BIT, ColorSystem.TRUECOLOR)
+
+ @property
+ def is_default(self) -> bool:
+ """Check if the color is a default color."""
+ return self.type == ColorType.DEFAULT
+
+ def get_truecolor(
+ self, theme: Optional["TerminalTheme"] = None, foreground: bool = True
+ ) -> ColorTriplet:
+ """Get an equivalent color triplet for this color.
+
+ Args:
+ theme (TerminalTheme, optional): Optional terminal theme, or None to use default. Defaults to None.
+ foreground (bool, optional): True for a foreground color, or False for background. Defaults to True.
+
+ Returns:
+ ColorTriplet: A color triplet containing RGB components.
+ """
+
+ if theme is None:
+ theme = DEFAULT_TERMINAL_THEME
+ if self.type == ColorType.TRUECOLOR:
+ assert self.triplet is not None
+ return self.triplet
+ elif self.type == ColorType.EIGHT_BIT:
+ assert self.number is not None
+ return EIGHT_BIT_PALETTE[self.number]
+ elif self.type == ColorType.STANDARD:
+ assert self.number is not None
+ return theme.ansi_colors[self.number]
+ elif self.type == ColorType.WINDOWS:
+ assert self.number is not None
+ return WINDOWS_PALETTE[self.number]
+ else: # self.type == ColorType.DEFAULT:
+ assert self.number is None
+ return theme.foreground_color if foreground else theme.background_color
+
+ @classmethod
+ def from_ansi(cls, number: int) -> "Color":
+ """Create a Color number from it's 8-bit ansi number.
+
+ Args:
+ number (int): A number between 0-255 inclusive.
+
+ Returns:
+ Color: A new Color instance.
+ """
+ return cls(
+ name=f"color({number})",
+ type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT),
+ number=number,
+ )
+
+ @classmethod
+ def from_triplet(cls, triplet: "ColorTriplet") -> "Color":
+ """Create a truecolor RGB color from a triplet of values.
+
+ Args:
+ triplet (ColorTriplet): A color triplet containing red, green and blue components.
+
+ Returns:
+ Color: A new color object.
+ """
+ return cls(name=triplet.hex, type=ColorType.TRUECOLOR, triplet=triplet)
+
+ @classmethod
+ def from_rgb(cls, red: float, green: float, blue: float) -> "Color":
+ """Create a truecolor from three color components in the range(0->255).
+
+ Args:
+ red (float): Red component in range 0-255.
+ green (float): Green component in range 0-255.
+ blue (float): Blue component in range 0-255.
+
+ Returns:
+ Color: A new color object.
+ """
+ return cls.from_triplet(ColorTriplet(int(red), int(green), int(blue)))
+
+ @classmethod
+ def default(cls) -> "Color":
+ """Get a Color instance representing the default color.
+
+ Returns:
+ Color: Default color.
+ """
+ return cls(name="default", type=ColorType.DEFAULT)
+
+ @classmethod
+ @lru_cache(maxsize=1024)
+ def parse(cls, color: str) -> "Color":
+ """Parse a color definition."""
+ original_color = color
+ color = color.lower().strip()
+
+ if color == "default":
+ return cls(color, type=ColorType.DEFAULT)
+
+ color_number = ANSI_COLOR_NAMES.get(color)
+ if color_number is not None:
+ return cls(
+ color,
+ type=(ColorType.STANDARD if color_number < 16 else ColorType.EIGHT_BIT),
+ number=color_number,
+ )
+
+ color_match = RE_COLOR.match(color)
+ if color_match is None:
+ raise ColorParseError(f"{original_color!r} is not a valid color")
+
+ color_24, color_8, color_rgb = color_match.groups()
+ if color_24:
+ triplet = ColorTriplet(
+ int(color_24[0:2], 16), int(color_24[2:4], 16), int(color_24[4:6], 16)
+ )
+ return cls(color, ColorType.TRUECOLOR, triplet=triplet)
+
+ elif color_8:
+ number = int(color_8)
+ if number > 255:
+ raise ColorParseError(f"color number must be <= 255 in {color!r}")
+ return cls(
+ color,
+ type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT),
+ number=number,
+ )
+
+ else: # color_rgb:
+ components = color_rgb.split(",")
+ if len(components) != 3:
+ raise ColorParseError(
+ f"expected three components in {original_color!r}"
+ )
+ red, green, blue = components
+ triplet = ColorTriplet(int(red), int(green), int(blue))
+ if not all(component <= 255 for component in triplet):
+ raise ColorParseError(
+ f"color components must be <= 255 in {original_color!r}"
+ )
+ return cls(color, ColorType.TRUECOLOR, triplet=triplet)
+
+ @lru_cache(maxsize=1024)
+ def get_ansi_codes(self, foreground: bool = True) -> Tuple[str, ...]:
+ """Get the ANSI escape codes for this color."""
+ _type = self.type
+ if _type == ColorType.DEFAULT:
+ return ("39" if foreground else "49",)
+
+ elif _type == ColorType.WINDOWS:
+ number = self.number
+ assert number is not None
+ fore, back = (30, 40) if number < 8 else (82, 92)
+ return (str(fore + number if foreground else back + number),)
+
+ elif _type == ColorType.STANDARD:
+ number = self.number
+ assert number is not None
+ fore, back = (30, 40) if number < 8 else (82, 92)
+ return (str(fore + number if foreground else back + number),)
+
+ elif _type == ColorType.EIGHT_BIT:
+ assert self.number is not None
+ return ("38" if foreground else "48", "5", str(self.number))
+
+ else: # self.standard == ColorStandard.TRUECOLOR:
+ assert self.triplet is not None
+ red, green, blue = self.triplet
+ return ("38" if foreground else "48", "2", str(red), str(green), str(blue))
+
+ @lru_cache(maxsize=1024)
+ def downgrade(self, system: ColorSystem) -> "Color":
+ """Downgrade a color system to a system with fewer colors."""
+
+ if self.type in [ColorType.DEFAULT, system]:
+ return self
+ # Convert to 8-bit color from truecolor color
+ if system == ColorSystem.EIGHT_BIT and self.system == ColorSystem.TRUECOLOR:
+ assert self.triplet is not None
+ red, green, blue = self.triplet.normalized
+ _h, l, s = rgb_to_hls(red, green, blue)
+ # If saturation is under 10% assume it is grayscale
+ if s < 0.1:
+ gray = round(l * 25.0)
+ if gray == 0:
+ color_number = 16
+ elif gray == 25:
+ color_number = 231
+ else:
+ color_number = 231 + gray
+ return Color(self.name, ColorType.EIGHT_BIT, number=color_number)
+
+ color_number = (
+ 16 + 36 * round(red * 5.0) + 6 * round(green * 5.0) + round(blue * 5.0)
+ )
+ return Color(self.name, ColorType.EIGHT_BIT, number=color_number)
+
+ # Convert to standard from truecolor or 8-bit
+ elif system == ColorSystem.STANDARD:
+ if self.system == ColorSystem.TRUECOLOR:
+ assert self.triplet is not None
+ triplet = self.triplet
+ else: # self.system == ColorSystem.EIGHT_BIT
+ assert self.number is not None
+ triplet = ColorTriplet(*EIGHT_BIT_PALETTE[self.number])
+
+ color_number = STANDARD_PALETTE.match(triplet)
+ return Color(self.name, ColorType.STANDARD, number=color_number)
+
+ elif system == ColorSystem.WINDOWS:
+ if self.system == ColorSystem.TRUECOLOR:
+ assert self.triplet is not None
+ triplet = self.triplet
+ else: # self.system == ColorSystem.EIGHT_BIT
+ assert self.number is not None
+ if self.number < 16:
+ return Color(self.name, ColorType.WINDOWS, number=self.number)
+ triplet = ColorTriplet(*EIGHT_BIT_PALETTE[self.number])
+
+ color_number = WINDOWS_PALETTE.match(triplet)
+ return Color(self.name, ColorType.WINDOWS, number=color_number)
+
+ return self
+
+
+def parse_rgb_hex(hex_color: str) -> ColorTriplet:
+ """Parse six hex characters in to RGB triplet."""
+ assert len(hex_color) == 6, "must be 6 characters"
+ color = ColorTriplet(
+ int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
+ )
+ return color
+
+
+def blend_rgb(
+ color1: ColorTriplet, color2: ColorTriplet, cross_fade: float = 0.5
+) -> ColorTriplet:
+ """Blend one RGB color in to another."""
+ r1, g1, b1 = color1
+ r2, g2, b2 = color2
+ new_color = ColorTriplet(
+ int(r1 + (r2 - r1) * cross_fade),
+ int(g1 + (g2 - g1) * cross_fade),
+ int(b1 + (b2 - b1) * cross_fade),
+ )
+ return new_color
+
+
+if __name__ == "__main__": # pragma: no cover
+
+ from .console import Console
+ from .table import Table
+ from .text import Text
+
+ console = Console()
+
+ table = Table(show_footer=False, show_edge=True)
+ table.add_column("Color", width=10, overflow="ellipsis")
+ table.add_column("Number", justify="right", style="yellow")
+ table.add_column("Name", style="green")
+ table.add_column("Hex", style="blue")
+ table.add_column("RGB", style="magenta")
+
+ colors = sorted((v, k) for k, v in ANSI_COLOR_NAMES.items())
+ for color_number, name in colors:
+ if "grey" in name:
+ continue
+ color_cell = Text(" " * 10, style=f"on {name}")
+ if color_number < 16:
+ table.add_row(color_cell, f"{color_number}", Text(f'"{name}"'))
+ else:
+ color = EIGHT_BIT_PALETTE[color_number] # type: ignore[has-type]
+ table.add_row(
+ color_cell, str(color_number), Text(f'"{name}"'), color.hex, color.rgb
+ )
+
+ console.print(table)
diff --git a/src/pip/_vendor/rich/color_triplet.py b/src/pip/_vendor/rich/color_triplet.py
new file mode 100644
index 000000000..02cab3282
--- /dev/null
+++ b/src/pip/_vendor/rich/color_triplet.py
@@ -0,0 +1,38 @@
+from typing import NamedTuple, Tuple
+
+
+class ColorTriplet(NamedTuple):
+ """The red, green, and blue components of a color."""
+
+ red: int
+ """Red component in 0 to 255 range."""
+ green: int
+ """Green component in 0 to 255 range."""
+ blue: int
+ """Blue component in 0 to 255 range."""
+
+ @property
+ def hex(self) -> str:
+ """get the color triplet in CSS style."""
+ red, green, blue = self
+ return f"#{red:02x}{green:02x}{blue:02x}"
+
+ @property
+ def rgb(self) -> str:
+ """The color in RGB format.
+
+ Returns:
+ str: An rgb color, e.g. ``"rgb(100,23,255)"``.
+ """
+ red, green, blue = self
+ return f"rgb({red},{green},{blue})"
+
+ @property
+ def normalized(self) -> Tuple[float, float, float]:
+ """Convert components into floats between 0 and 1.
+
+ Returns:
+ Tuple[float, float, float]: A tuple of three normalized colour components.
+ """
+ red, green, blue = self
+ return red / 255.0, green / 255.0, blue / 255.0
diff --git a/src/pip/_vendor/rich/columns.py b/src/pip/_vendor/rich/columns.py
new file mode 100644
index 000000000..669a3a707
--- /dev/null
+++ b/src/pip/_vendor/rich/columns.py
@@ -0,0 +1,187 @@
+from collections import defaultdict
+from itertools import chain
+from operator import itemgetter
+from typing import Dict, Iterable, List, Optional, Tuple
+
+from .align import Align, AlignMethod
+from .console import Console, ConsoleOptions, RenderableType, RenderResult
+from .constrain import Constrain
+from .measure import Measurement
+from .padding import Padding, PaddingDimensions
+from .table import Table
+from .text import TextType
+from .jupyter import JupyterMixin
+
+
+class Columns(JupyterMixin):
+ """Display renderables in neat columns.
+
+ Args:
+ renderables (Iterable[RenderableType]): Any number of Rich renderables (including str).
+ width (int, optional): The desired width of the columns, or None to auto detect. Defaults to None.
+ padding (PaddingDimensions, optional): Optional padding around cells. Defaults to (0, 1).
+ expand (bool, optional): Expand columns to full width. Defaults to False.
+ equal (bool, optional): Arrange in to equal sized columns. Defaults to False.
+ column_first (bool, optional): Align items from top to bottom (rather than left to right). Defaults to False.
+ right_to_left (bool, optional): Start column from right hand side. Defaults to False.
+ align (str, optional): Align value ("left", "right", or "center") or None for default. Defaults to None.
+ title (TextType, optional): Optional title for Columns.
+ """
+
+ def __init__(
+ self,
+ renderables: Optional[Iterable[RenderableType]] = None,
+ padding: PaddingDimensions = (0, 1),
+ *,
+ width: Optional[int] = None,
+ expand: bool = False,
+ equal: bool = False,
+ column_first: bool = False,
+ right_to_left: bool = False,
+ align: Optional[AlignMethod] = None,
+ title: Optional[TextType] = None,
+ ) -> None:
+ self.renderables = list(renderables or [])
+ self.width = width
+ self.padding = padding
+ self.expand = expand
+ self.equal = equal
+ self.column_first = column_first
+ self.right_to_left = right_to_left
+ self.align: Optional[AlignMethod] = align
+ self.title = title
+
+ def add_renderable(self, renderable: RenderableType) -> None:
+ """Add a renderable to the columns.
+
+ Args:
+ renderable (RenderableType): Any renderable object.
+ """
+ self.renderables.append(renderable)
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ render_str = console.render_str
+ renderables = [
+ render_str(renderable) if isinstance(renderable, str) else renderable
+ for renderable in self.renderables
+ ]
+ if not renderables:
+ return
+ _top, right, _bottom, left = Padding.unpack(self.padding)
+ width_padding = max(left, right)
+ max_width = options.max_width
+ widths: Dict[int, int] = defaultdict(int)
+ column_count = len(renderables)
+
+ get_measurement = Measurement.get
+ renderable_widths = [
+ get_measurement(console, options, renderable).maximum
+ for renderable in renderables
+ ]
+ if self.equal:
+ renderable_widths = [max(renderable_widths)] * len(renderable_widths)
+
+ def iter_renderables(
+ column_count: int,
+ ) -> Iterable[Tuple[int, Optional[RenderableType]]]:
+ item_count = len(renderables)
+ if self.column_first:
+ width_renderables = list(zip(renderable_widths, renderables))
+
+ column_lengths: List[int] = [item_count // column_count] * column_count
+ for col_no in range(item_count % column_count):
+ column_lengths[col_no] += 1
+
+ row_count = (item_count + column_count - 1) // column_count
+ cells = [[-1] * column_count for _ in range(row_count)]
+ row = col = 0
+ for index in range(item_count):
+ cells[row][col] = index
+ column_lengths[col] -= 1
+ if column_lengths[col]:
+ row += 1
+ else:
+ col += 1
+ row = 0
+ for index in chain.from_iterable(cells):
+ if index == -1:
+ break
+ yield width_renderables[index]
+ else:
+ yield from zip(renderable_widths, renderables)
+ # Pad odd elements with spaces
+ if item_count % column_count:
+ for _ in range(column_count - (item_count % column_count)):
+ yield 0, None
+
+ table = Table.grid(padding=self.padding, collapse_padding=True, pad_edge=False)
+ table.expand = self.expand
+ table.title = self.title
+
+ if self.width is not None:
+ column_count = (max_width) // (self.width + width_padding)
+ for _ in range(column_count):
+ table.add_column(width=self.width)
+ else:
+ while column_count > 1:
+ widths.clear()
+ column_no = 0
+ for renderable_width, _ in iter_renderables(column_count):
+ widths[column_no] = max(widths[column_no], renderable_width)
+ total_width = sum(widths.values()) + width_padding * (
+ len(widths) - 1
+ )
+ if total_width > max_width:
+ column_count = len(widths) - 1
+ break
+ else:
+ column_no = (column_no + 1) % column_count
+ else:
+ break
+
+ get_renderable = itemgetter(1)
+ _renderables = [
+ get_renderable(_renderable)
+ for _renderable in iter_renderables(column_count)
+ ]
+ if self.equal:
+ _renderables = [
+ None
+ if renderable is None
+ else Constrain(renderable, renderable_widths[0])
+ for renderable in _renderables
+ ]
+ if self.align:
+ align = self.align
+ _Align = Align
+ _renderables = [
+ None if renderable is None else _Align(renderable, align)
+ for renderable in _renderables
+ ]
+
+ right_to_left = self.right_to_left
+ add_row = table.add_row
+ for start in range(0, len(_renderables), column_count):
+ row = _renderables[start : start + column_count]
+ if right_to_left:
+ row = row[::-1]
+ add_row(*row)
+ yield table
+
+
+if __name__ == "__main__": # pragma: no cover
+ import os
+
+ console = Console()
+
+ files = [f"{i} {s}" for i, s in enumerate(sorted(os.listdir()))]
+ columns = Columns(files, padding=(0, 1), expand=False, equal=False)
+ console.print(columns)
+ console.rule()
+ columns.column_first = True
+ console.print(columns)
+ columns.right_to_left = True
+ console.rule()
+ console.print(columns)
diff --git a/src/pip/_vendor/rich/console.py b/src/pip/_vendor/rich/console.py
new file mode 100644
index 000000000..f805f2dea
--- /dev/null
+++ b/src/pip/_vendor/rich/console.py
@@ -0,0 +1,2612 @@
+import inspect
+import io
+import os
+import platform
+import sys
+import threading
+import zlib
+from abc import ABC, abstractmethod
+from dataclasses import dataclass, field
+from datetime import datetime
+from functools import wraps
+from getpass import getpass
+from html import escape
+from inspect import isclass
+from itertools import islice
+from math import ceil
+from time import monotonic
+from types import FrameType, ModuleType, TracebackType
+from typing import (
+ IO,
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Dict,
+ Iterable,
+ List,
+ Mapping,
+ NamedTuple,
+ Optional,
+ TextIO,
+ Tuple,
+ Type,
+ Union,
+ cast,
+)
+
+from pip._vendor.rich._null_file import NULL_FILE
+
+if sys.version_info >= (3, 8):
+ from typing import Literal, Protocol, runtime_checkable
+else:
+ from pip._vendor.typing_extensions import (
+ Literal,
+ Protocol,
+ runtime_checkable,
+ ) # pragma: no cover
+
+from . import errors, themes
+from ._emoji_replace import _emoji_replace
+from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT
+from ._log_render import FormatTimeCallable, LogRender
+from .align import Align, AlignMethod
+from .color import ColorSystem, blend_rgb
+from .control import Control
+from .emoji import EmojiVariant
+from .highlighter import NullHighlighter, ReprHighlighter
+from .markup import render as render_markup
+from .measure import Measurement, measure_renderables
+from .pager import Pager, SystemPager
+from .pretty import Pretty, is_expandable
+from .protocol import rich_cast
+from .region import Region
+from .scope import render_scope
+from .screen import Screen
+from .segment import Segment
+from .style import Style, StyleType
+from .styled import Styled
+from .terminal_theme import DEFAULT_TERMINAL_THEME, SVG_EXPORT_THEME, TerminalTheme
+from .text import Text, TextType
+from .theme import Theme, ThemeStack
+
+if TYPE_CHECKING:
+ from ._windows import WindowsConsoleFeatures
+ from .live import Live
+ from .status import Status
+
+JUPYTER_DEFAULT_COLUMNS = 115
+JUPYTER_DEFAULT_LINES = 100
+WINDOWS = platform.system() == "Windows"
+
+HighlighterType = Callable[[Union[str, "Text"]], "Text"]
+JustifyMethod = Literal["default", "left", "center", "right", "full"]
+OverflowMethod = Literal["fold", "crop", "ellipsis", "ignore"]
+
+
+class NoChange:
+ pass
+
+
+NO_CHANGE = NoChange()
+
+try:
+ _STDIN_FILENO = sys.__stdin__.fileno()
+except Exception:
+ _STDIN_FILENO = 0
+try:
+ _STDOUT_FILENO = sys.__stdout__.fileno()
+except Exception:
+ _STDOUT_FILENO = 1
+try:
+ _STDERR_FILENO = sys.__stderr__.fileno()
+except Exception:
+ _STDERR_FILENO = 2
+
+_STD_STREAMS = (_STDIN_FILENO, _STDOUT_FILENO, _STDERR_FILENO)
+_STD_STREAMS_OUTPUT = (_STDOUT_FILENO, _STDERR_FILENO)
+
+
+_TERM_COLORS = {
+ "kitty": ColorSystem.EIGHT_BIT,
+ "256color": ColorSystem.EIGHT_BIT,
+ "16color": ColorSystem.STANDARD,
+}
+
+
+class ConsoleDimensions(NamedTuple):
+ """Size of the terminal."""
+
+ width: int
+ """The width of the console in 'cells'."""
+ height: int
+ """The height of the console in lines."""
+
+
+@dataclass
+class ConsoleOptions:
+ """Options for __rich_console__ method."""
+
+ size: ConsoleDimensions
+ """Size of console."""
+ legacy_windows: bool
+ """legacy_windows: flag for legacy windows."""
+ min_width: int
+ """Minimum width of renderable."""
+ max_width: int
+ """Maximum width of renderable."""
+ is_terminal: bool
+ """True if the target is a terminal, otherwise False."""
+ encoding: str
+ """Encoding of terminal."""
+ max_height: int
+ """Height of container (starts as terminal)"""
+ justify: Optional[JustifyMethod] = None
+ """Justify value override for renderable."""
+ overflow: Optional[OverflowMethod] = None
+ """Overflow value override for renderable."""
+ no_wrap: Optional[bool] = False
+ """Disable wrapping for text."""
+ highlight: Optional[bool] = None
+ """Highlight override for render_str."""
+ markup: Optional[bool] = None
+ """Enable markup when rendering strings."""
+ height: Optional[int] = None
+
+ @property
+ def ascii_only(self) -> bool:
+ """Check if renderables should use ascii only."""
+ return not self.encoding.startswith("utf")
+
+ def copy(self) -> "ConsoleOptions":
+ """Return a copy of the options.
+
+ Returns:
+ ConsoleOptions: a copy of self.
+ """
+ options: ConsoleOptions = ConsoleOptions.__new__(ConsoleOptions)
+ options.__dict__ = self.__dict__.copy()
+ return options
+
+ def update(
+ self,
+ *,
+ width: Union[int, NoChange] = NO_CHANGE,
+ min_width: Union[int, NoChange] = NO_CHANGE,
+ max_width: Union[int, NoChange] = NO_CHANGE,
+ justify: Union[Optional[JustifyMethod], NoChange] = NO_CHANGE,
+ overflow: Union[Optional[OverflowMethod], NoChange] = NO_CHANGE,
+ no_wrap: Union[Optional[bool], NoChange] = NO_CHANGE,
+ highlight: Union[Optional[bool], NoChange] = NO_CHANGE,
+ markup: Union[Optional[bool], NoChange] = NO_CHANGE,
+ height: Union[Optional[int], NoChange] = NO_CHANGE,
+ ) -> "ConsoleOptions":
+ """Update values, return a copy."""
+ options = self.copy()
+ if not isinstance(width, NoChange):
+ options.min_width = options.max_width = max(0, width)
+ if not isinstance(min_width, NoChange):
+ options.min_width = min_width
+ if not isinstance(max_width, NoChange):
+ options.max_width = max_width
+ if not isinstance(justify, NoChange):
+ options.justify = justify
+ if not isinstance(overflow, NoChange):
+ options.overflow = overflow
+ if not isinstance(no_wrap, NoChange):
+ options.no_wrap = no_wrap
+ if not isinstance(highlight, NoChange):
+ options.highlight = highlight
+ if not isinstance(markup, NoChange):
+ options.markup = markup
+ if not isinstance(height, NoChange):
+ if height is not None:
+ options.max_height = height
+ options.height = None if height is None else max(0, height)
+ return options
+
+ def update_width(self, width: int) -> "ConsoleOptions":
+ """Update just the width, return a copy.
+
+ Args:
+ width (int): New width (sets both min_width and max_width)
+
+ Returns:
+ ~ConsoleOptions: New console options instance.
+ """
+ options = self.copy()
+ options.min_width = options.max_width = max(0, width)
+ return options
+
+ def update_height(self, height: int) -> "ConsoleOptions":
+ """Update the height, and return a copy.
+
+ Args:
+ height (int): New height
+
+ Returns:
+ ~ConsoleOptions: New Console options instance.
+ """
+ options = self.copy()
+ options.max_height = options.height = height
+ return options
+
+ def reset_height(self) -> "ConsoleOptions":
+ """Return a copy of the options with height set to ``None``.
+
+ Returns:
+ ~ConsoleOptions: New console options instance.
+ """
+ options = self.copy()
+ options.height = None
+ return options
+
+ def update_dimensions(self, width: int, height: int) -> "ConsoleOptions":
+ """Update the width and height, and return a copy.
+
+ Args:
+ width (int): New width (sets both min_width and max_width).
+ height (int): New height.
+
+ Returns:
+ ~ConsoleOptions: New console options instance.
+ """
+ options = self.copy()
+ options.min_width = options.max_width = max(0, width)
+ options.height = options.max_height = height
+ return options
+
+
+@runtime_checkable
+class RichCast(Protocol):
+ """An object that may be 'cast' to a console renderable."""
+
+ def __rich__(
+ self,
+ ) -> Union["ConsoleRenderable", "RichCast", str]: # pragma: no cover
+ ...
+
+
+@runtime_checkable
+class ConsoleRenderable(Protocol):
+ """An object that supports the console protocol."""
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult": # pragma: no cover
+ ...
+
+
+# A type that may be rendered by Console.
+RenderableType = Union[ConsoleRenderable, RichCast, str]
+
+# The result of calling a __rich_console__ method.
+RenderResult = Iterable[Union[RenderableType, Segment]]
+
+_null_highlighter = NullHighlighter()
+
+
+class CaptureError(Exception):
+ """An error in the Capture context manager."""
+
+
+class NewLine:
+ """A renderable to generate new line(s)"""
+
+ def __init__(self, count: int = 1) -> None:
+ self.count = count
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> Iterable[Segment]:
+ yield Segment("\n" * self.count)
+
+
+class ScreenUpdate:
+ """Render a list of lines at a given offset."""
+
+ def __init__(self, lines: List[List[Segment]], x: int, y: int) -> None:
+ self._lines = lines
+ self.x = x
+ self.y = y
+
+ def __rich_console__(
+ self, console: "Console", options: ConsoleOptions
+ ) -> RenderResult:
+ x = self.x
+ move_to = Control.move_to
+ for offset, line in enumerate(self._lines, self.y):
+ yield move_to(x, offset)
+ yield from line
+
+
+class Capture:
+ """Context manager to capture the result of printing to the console.
+ See :meth:`~rich.console.Console.capture` for how to use.
+
+ Args:
+ console (Console): A console instance to capture output.
+ """
+
+ def __init__(self, console: "Console") -> None:
+ self._console = console
+ self._result: Optional[str] = None
+
+ def __enter__(self) -> "Capture":
+ self._console.begin_capture()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ self._result = self._console.end_capture()
+
+ def get(self) -> str:
+ """Get the result of the capture."""
+ if self._result is None:
+ raise CaptureError(
+ "Capture result is not available until context manager exits."
+ )
+ return self._result
+
+
+class ThemeContext:
+ """A context manager to use a temporary theme. See :meth:`~rich.console.Console.use_theme` for usage."""
+
+ def __init__(self, console: "Console", theme: Theme, inherit: bool = True) -> None:
+ self.console = console
+ self.theme = theme
+ self.inherit = inherit
+
+ def __enter__(self) -> "ThemeContext":
+ self.console.push_theme(self.theme)
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ self.console.pop_theme()
+
+
+class PagerContext:
+ """A context manager that 'pages' content. See :meth:`~rich.console.Console.pager` for usage."""
+
+ def __init__(
+ self,
+ console: "Console",
+ pager: Optional[Pager] = None,
+ styles: bool = False,
+ links: bool = False,
+ ) -> None:
+ self._console = console
+ self.pager = SystemPager() if pager is None else pager
+ self.styles = styles
+ self.links = links
+
+ def __enter__(self) -> "PagerContext":
+ self._console._enter_buffer()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ if exc_type is None:
+ with self._console._lock:
+ buffer: List[Segment] = self._console._buffer[:]
+ del self._console._buffer[:]
+ segments: Iterable[Segment] = buffer
+ if not self.styles:
+ segments = Segment.strip_styles(segments)
+ elif not self.links:
+ segments = Segment.strip_links(segments)
+ content = self._console._render_buffer(segments)
+ self.pager.show(content)
+ self._console._exit_buffer()
+
+
+class ScreenContext:
+ """A context manager that enables an alternative screen. See :meth:`~rich.console.Console.screen` for usage."""
+
+ def __init__(
+ self, console: "Console", hide_cursor: bool, style: StyleType = ""
+ ) -> None:
+ self.console = console
+ self.hide_cursor = hide_cursor
+ self.screen = Screen(style=style)
+ self._changed = False
+
+ def update(
+ self, *renderables: RenderableType, style: Optional[StyleType] = None
+ ) -> None:
+ """Update the screen.
+
+ Args:
+ renderable (RenderableType, optional): Optional renderable to replace current renderable,
+ or None for no change. Defaults to None.
+ style: (Style, optional): Replacement style, or None for no change. Defaults to None.
+ """
+ if renderables:
+ self.screen.renderable = (
+ Group(*renderables) if len(renderables) > 1 else renderables[0]
+ )
+ if style is not None:
+ self.screen.style = style
+ self.console.print(self.screen, end="")
+
+ def __enter__(self) -> "ScreenContext":
+ self._changed = self.console.set_alt_screen(True)
+ if self._changed and self.hide_cursor:
+ self.console.show_cursor(False)
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ if self._changed:
+ self.console.set_alt_screen(False)
+ if self.hide_cursor:
+ self.console.show_cursor(True)
+
+
+class Group:
+ """Takes a group of renderables and returns a renderable object that renders the group.
+
+ Args:
+ renderables (Iterable[RenderableType]): An iterable of renderable objects.
+ fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True.
+ """
+
+ def __init__(self, *renderables: "RenderableType", fit: bool = True) -> None:
+ self._renderables = renderables
+ self.fit = fit
+ self._render: Optional[List[RenderableType]] = None
+
+ @property
+ def renderables(self) -> List["RenderableType"]:
+ if self._render is None:
+ self._render = list(self._renderables)
+ return self._render
+
+ def __rich_measure__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "Measurement":
+ if self.fit:
+ return measure_renderables(console, options, self.renderables)
+ else:
+ return Measurement(options.max_width, options.max_width)
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> RenderResult:
+ yield from self.renderables
+
+
+def group(fit: bool = True) -> Callable[..., Callable[..., Group]]:
+ """A decorator that turns an iterable of renderables in to a group.
+
+ Args:
+ fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True.
+ """
+
+ def decorator(
+ method: Callable[..., Iterable[RenderableType]]
+ ) -> Callable[..., Group]:
+ """Convert a method that returns an iterable of renderables in to a Group."""
+
+ @wraps(method)
+ def _replace(*args: Any, **kwargs: Any) -> Group:
+ renderables = method(*args, **kwargs)
+ return Group(*renderables, fit=fit)
+
+ return _replace
+
+ return decorator
+
+
+def _is_jupyter() -> bool: # pragma: no cover
+ """Check if we're running in a Jupyter notebook."""
+ try:
+ get_ipython # type: ignore[name-defined]
+ except NameError:
+ return False
+ ipython = get_ipython() # type: ignore[name-defined]
+ shell = ipython.__class__.__name__
+ if (
+ "google.colab" in str(ipython.__class__)
+ or os.getenv("DATABRICKS_RUNTIME_VERSION")
+ or shell == "ZMQInteractiveShell"
+ ):
+ return True # Jupyter notebook or qtconsole
+ elif shell == "TerminalInteractiveShell":
+ return False # Terminal running IPython
+ else:
+ return False # Other type (?)
+
+
+COLOR_SYSTEMS = {
+ "standard": ColorSystem.STANDARD,
+ "256": ColorSystem.EIGHT_BIT,
+ "truecolor": ColorSystem.TRUECOLOR,
+ "windows": ColorSystem.WINDOWS,
+}
+
+_COLOR_SYSTEMS_NAMES = {system: name for name, system in COLOR_SYSTEMS.items()}
+
+
+@dataclass
+class ConsoleThreadLocals(threading.local):
+ """Thread local values for Console context."""
+
+ theme_stack: ThemeStack
+ buffer: List[Segment] = field(default_factory=list)
+ buffer_index: int = 0
+
+
+class RenderHook(ABC):
+ """Provides hooks in to the render process."""
+
+ @abstractmethod
+ def process_renderables(
+ self, renderables: List[ConsoleRenderable]
+ ) -> List[ConsoleRenderable]:
+ """Called with a list of objects to render.
+
+ This method can return a new list of renderables, or modify and return the same list.
+
+ Args:
+ renderables (List[ConsoleRenderable]): A number of renderable objects.
+
+ Returns:
+ List[ConsoleRenderable]: A replacement list of renderables.
+ """
+
+
+_windows_console_features: Optional["WindowsConsoleFeatures"] = None
+
+
+def get_windows_console_features() -> "WindowsConsoleFeatures": # pragma: no cover
+ global _windows_console_features
+ if _windows_console_features is not None:
+ return _windows_console_features
+ from ._windows import get_windows_console_features
+
+ _windows_console_features = get_windows_console_features()
+ return _windows_console_features
+
+
+def detect_legacy_windows() -> bool:
+ """Detect legacy Windows."""
+ return WINDOWS and not get_windows_console_features().vt
+
+
+class Console:
+ """A high level console interface.
+
+ Args:
+ color_system (str, optional): The color system supported by your terminal,
+ either ``"standard"``, ``"256"`` or ``"truecolor"``. Leave as ``"auto"`` to autodetect.
+ force_terminal (Optional[bool], optional): Enable/disable terminal control codes, or None to auto-detect terminal. Defaults to None.
+ force_jupyter (Optional[bool], optional): Enable/disable Jupyter rendering, or None to auto-detect Jupyter. Defaults to None.
+ force_interactive (Optional[bool], optional): Enable/disable interactive mode, or None to auto detect. Defaults to None.
+ soft_wrap (Optional[bool], optional): Set soft wrap default on print method. Defaults to False.
+ theme (Theme, optional): An optional style theme object, or ``None`` for default theme.
+ stderr (bool, optional): Use stderr rather than stdout if ``file`` is not specified. Defaults to False.
+ file (IO, optional): A file object where the console should write to. Defaults to stdout.
+ quiet (bool, Optional): Boolean to suppress all output. Defaults to False.
+ width (int, optional): The width of the terminal. Leave as default to auto-detect width.
+ height (int, optional): The height of the terminal. Leave as default to auto-detect height.
+ style (StyleType, optional): Style to apply to all output, or None for no style. Defaults to None.
+ no_color (Optional[bool], optional): Enabled no color mode, or None to auto detect. Defaults to None.
+ tab_size (int, optional): Number of spaces used to replace a tab character. Defaults to 8.
+ record (bool, optional): Boolean to enable recording of terminal output,
+ required to call :meth:`export_html`, :meth:`export_svg`, and :meth:`export_text`. Defaults to False.
+ markup (bool, optional): Boolean to enable :ref:`console_markup`. Defaults to True.
+ emoji (bool, optional): Enable emoji code. Defaults to True.
+ emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None.
+ highlight (bool, optional): Enable automatic highlighting. Defaults to True.
+ log_time (bool, optional): Boolean to enable logging of time by :meth:`log` methods. Defaults to True.
+ log_path (bool, optional): Boolean to enable the logging of the caller by :meth:`log`. Defaults to True.
+ log_time_format (Union[str, TimeFormatterCallable], optional): If ``log_time`` is enabled, either string for strftime or callable that formats the time. Defaults to "[%X] ".
+ highlighter (HighlighterType, optional): Default highlighter.
+ legacy_windows (bool, optional): Enable legacy Windows mode, or ``None`` to auto detect. Defaults to ``None``.
+ safe_box (bool, optional): Restrict box options that don't render on legacy Windows.
+ get_datetime (Callable[[], datetime], optional): Callable that gets the current time as a datetime.datetime object (used by Console.log),
+ or None for datetime.now.
+ get_time (Callable[[], time], optional): Callable that gets the current time in seconds, default uses time.monotonic.
+ """
+
+ _environ: Mapping[str, str] = os.environ
+
+ def __init__(
+ self,
+ *,
+ color_system: Optional[
+ Literal["auto", "standard", "256", "truecolor", "windows"]
+ ] = "auto",
+ force_terminal: Optional[bool] = None,
+ force_jupyter: Optional[bool] = None,
+ force_interactive: Optional[bool] = None,
+ soft_wrap: bool = False,
+ theme: Optional[Theme] = None,
+ stderr: bool = False,
+ file: Optional[IO[str]] = None,
+ quiet: bool = False,
+ width: Optional[int] = None,
+ height: Optional[int] = None,
+ style: Optional[StyleType] = None,
+ no_color: Optional[bool] = None,
+ tab_size: int = 8,
+ record: bool = False,
+ markup: bool = True,
+ emoji: bool = True,
+ emoji_variant: Optional[EmojiVariant] = None,
+ highlight: bool = True,
+ log_time: bool = True,
+ log_path: bool = True,
+ log_time_format: Union[str, FormatTimeCallable] = "[%X]",
+ highlighter: Optional["HighlighterType"] = ReprHighlighter(),
+ legacy_windows: Optional[bool] = None,
+ safe_box: bool = True,
+ get_datetime: Optional[Callable[[], datetime]] = None,
+ get_time: Optional[Callable[[], float]] = None,
+ _environ: Optional[Mapping[str, str]] = None,
+ ):
+ # Copy of os.environ allows us to replace it for testing
+ if _environ is not None:
+ self._environ = _environ
+
+ self.is_jupyter = _is_jupyter() if force_jupyter is None else force_jupyter
+ if self.is_jupyter:
+ if width is None:
+ jupyter_columns = self._environ.get("JUPYTER_COLUMNS")
+ if jupyter_columns is not None and jupyter_columns.isdigit():
+ width = int(jupyter_columns)
+ else:
+ width = JUPYTER_DEFAULT_COLUMNS
+ if height is None:
+ jupyter_lines = self._environ.get("JUPYTER_LINES")
+ if jupyter_lines is not None and jupyter_lines.isdigit():
+ height = int(jupyter_lines)
+ else:
+ height = JUPYTER_DEFAULT_LINES
+
+ self.tab_size = tab_size
+ self.record = record
+ self._markup = markup
+ self._emoji = emoji
+ self._emoji_variant: Optional[EmojiVariant] = emoji_variant
+ self._highlight = highlight
+ self.legacy_windows: bool = (
+ (detect_legacy_windows() and not self.is_jupyter)
+ if legacy_windows is None
+ else legacy_windows
+ )
+
+ if width is None:
+ columns = self._environ.get("COLUMNS")
+ if columns is not None and columns.isdigit():
+ width = int(columns) - self.legacy_windows
+ if height is None:
+ lines = self._environ.get("LINES")
+ if lines is not None and lines.isdigit():
+ height = int(lines)
+
+ self.soft_wrap = soft_wrap
+ self._width = width
+ self._height = height
+
+ self._color_system: Optional[ColorSystem]
+
+ self._force_terminal = None
+ if force_terminal is not None:
+ self._force_terminal = force_terminal
+ else:
+ # If FORCE_COLOR env var has any value at all, we force terminal.
+ force_color = self._environ.get("FORCE_COLOR")
+ if force_color is not None:
+ self._force_terminal = True
+
+ self._file = file
+ self.quiet = quiet
+ self.stderr = stderr
+
+ if color_system is None:
+ self._color_system = None
+ elif color_system == "auto":
+ self._color_system = self._detect_color_system()
+ else:
+ self._color_system = COLOR_SYSTEMS[color_system]
+
+ self._lock = threading.RLock()
+ self._log_render = LogRender(
+ show_time=log_time,
+ show_path=log_path,
+ time_format=log_time_format,
+ )
+ self.highlighter: HighlighterType = highlighter or _null_highlighter
+ self.safe_box = safe_box
+ self.get_datetime = get_datetime or datetime.now
+ self.get_time = get_time or monotonic
+ self.style = style
+ self.no_color = (
+ no_color if no_color is not None else "NO_COLOR" in self._environ
+ )
+ self.is_interactive = (
+ (self.is_terminal and not self.is_dumb_terminal)
+ if force_interactive is None
+ else force_interactive
+ )
+
+ self._record_buffer_lock = threading.RLock()
+ self._thread_locals = ConsoleThreadLocals(
+ theme_stack=ThemeStack(themes.DEFAULT if theme is None else theme)
+ )
+ self._record_buffer: List[Segment] = []
+ self._render_hooks: List[RenderHook] = []
+ self._live: Optional["Live"] = None
+ self._is_alt_screen = False
+
+ def __repr__(self) -> str:
+ return f"<console width={self.width} {str(self._color_system)}>"
+
+ @property
+ def file(self) -> IO[str]:
+ """Get the file object to write to."""
+ file = self._file or (sys.stderr if self.stderr else sys.stdout)
+ file = getattr(file, "rich_proxied_file", file)
+ if file is None:
+ file = NULL_FILE
+ return file
+
+ @file.setter
+ def file(self, new_file: IO[str]) -> None:
+ """Set a new file object."""
+ self._file = new_file
+
+ @property
+ def _buffer(self) -> List[Segment]:
+ """Get a thread local buffer."""
+ return self._thread_locals.buffer
+
+ @property
+ def _buffer_index(self) -> int:
+ """Get a thread local buffer."""
+ return self._thread_locals.buffer_index
+
+ @_buffer_index.setter
+ def _buffer_index(self, value: int) -> None:
+ self._thread_locals.buffer_index = value
+
+ @property
+ def _theme_stack(self) -> ThemeStack:
+ """Get the thread local theme stack."""
+ return self._thread_locals.theme_stack
+
+ def _detect_color_system(self) -> Optional[ColorSystem]:
+ """Detect color system from env vars."""
+ if self.is_jupyter:
+ return ColorSystem.TRUECOLOR
+ if not self.is_terminal or self.is_dumb_terminal:
+ return None
+ if WINDOWS: # pragma: no cover
+ if self.legacy_windows: # pragma: no cover
+ return ColorSystem.WINDOWS
+ windows_console_features = get_windows_console_features()
+ return (
+ ColorSystem.TRUECOLOR
+ if windows_console_features.truecolor
+ else ColorSystem.EIGHT_BIT
+ )
+ else:
+ color_term = self._environ.get("COLORTERM", "").strip().lower()
+ if color_term in ("truecolor", "24bit"):
+ return ColorSystem.TRUECOLOR
+ term = self._environ.get("TERM", "").strip().lower()
+ _term_name, _hyphen, colors = term.rpartition("-")
+ color_system = _TERM_COLORS.get(colors, ColorSystem.STANDARD)
+ return color_system
+
+ def _enter_buffer(self) -> None:
+ """Enter in to a buffer context, and buffer all output."""
+ self._buffer_index += 1
+
+ def _exit_buffer(self) -> None:
+ """Leave buffer context, and render content if required."""
+ self._buffer_index -= 1
+ self._check_buffer()
+
+ def set_live(self, live: "Live") -> None:
+ """Set Live instance. Used by Live context manager.
+
+ Args:
+ live (Live): Live instance using this Console.
+
+ Raises:
+ errors.LiveError: If this Console has a Live context currently active.
+ """
+ with self._lock:
+ if self._live is not None:
+ raise errors.LiveError("Only one live display may be active at once")
+ self._live = live
+
+ def clear_live(self) -> None:
+ """Clear the Live instance."""
+ with self._lock:
+ self._live = None
+
+ def push_render_hook(self, hook: RenderHook) -> None:
+ """Add a new render hook to the stack.
+
+ Args:
+ hook (RenderHook): Render hook instance.
+ """
+ with self._lock:
+ self._render_hooks.append(hook)
+
+ def pop_render_hook(self) -> None:
+ """Pop the last renderhook from the stack."""
+ with self._lock:
+ self._render_hooks.pop()
+
+ def __enter__(self) -> "Console":
+ """Own context manager to enter buffer context."""
+ self._enter_buffer()
+ return self
+
+ def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
+ """Exit buffer context."""
+ self._exit_buffer()
+
+ def begin_capture(self) -> None:
+ """Begin capturing console output. Call :meth:`end_capture` to exit capture mode and return output."""
+ self._enter_buffer()
+
+ def end_capture(self) -> str:
+ """End capture mode and return captured string.
+
+ Returns:
+ str: Console output.
+ """
+ render_result = self._render_buffer(self._buffer)
+ del self._buffer[:]
+ self._exit_buffer()
+ return render_result
+
+ def push_theme(self, theme: Theme, *, inherit: bool = True) -> None:
+ """Push a new theme on to the top of the stack, replacing the styles from the previous theme.
+ Generally speaking, you should call :meth:`~rich.console.Console.use_theme` to get a context manager, rather
+ than calling this method directly.
+
+ Args:
+ theme (Theme): A theme instance.
+ inherit (bool, optional): Inherit existing styles. Defaults to True.
+ """
+ self._theme_stack.push_theme(theme, inherit=inherit)
+
+ def pop_theme(self) -> None:
+ """Remove theme from top of stack, restoring previous theme."""
+ self._theme_stack.pop_theme()
+
+ def use_theme(self, theme: Theme, *, inherit: bool = True) -> ThemeContext:
+ """Use a different theme for the duration of the context manager.
+
+ Args:
+ theme (Theme): Theme instance to user.
+ inherit (bool, optional): Inherit existing console styles. Defaults to True.
+
+ Returns:
+ ThemeContext: [description]
+ """
+ return ThemeContext(self, theme, inherit)
+
+ @property
+ def color_system(self) -> Optional[str]:
+ """Get color system string.
+
+ Returns:
+ Optional[str]: "standard", "256" or "truecolor".
+ """
+
+ if self._color_system is not None:
+ return _COLOR_SYSTEMS_NAMES[self._color_system]
+ else:
+ return None
+
+ @property
+ def encoding(self) -> str:
+ """Get the encoding of the console file, e.g. ``"utf-8"``.
+
+ Returns:
+ str: A standard encoding string.
+ """
+ return (getattr(self.file, "encoding", "utf-8") or "utf-8").lower()
+
+ @property
+ def is_terminal(self) -> bool:
+ """Check if the console is writing to a terminal.
+
+ Returns:
+ bool: True if the console writing to a device capable of
+ understanding terminal codes, otherwise False.
+ """
+ if self._force_terminal is not None:
+ return self._force_terminal
+
+ if hasattr(sys.stdin, "__module__") and sys.stdin.__module__.startswith(
+ "idlelib"
+ ):
+ # Return False for Idle which claims to be a tty but can't handle ansi codes
+ return False
+
+ isatty: Optional[Callable[[], bool]] = getattr(self.file, "isatty", None)
+ try:
+ return False if isatty is None else isatty()
+ except ValueError:
+ # in some situation (at the end of a pytest run for example) isatty() can raise
+ # ValueError: I/O operation on closed file
+ # return False because we aren't in a terminal anymore
+ return False
+
+ @property
+ def is_dumb_terminal(self) -> bool:
+ """Detect dumb terminal.
+
+ Returns:
+ bool: True if writing to a dumb terminal, otherwise False.
+
+ """
+ _term = self._environ.get("TERM", "")
+ is_dumb = _term.lower() in ("dumb", "unknown")
+ return self.is_terminal and is_dumb
+
+ @property
+ def options(self) -> ConsoleOptions:
+ """Get default console options."""
+ return ConsoleOptions(
+ max_height=self.size.height,
+ size=self.size,
+ legacy_windows=self.legacy_windows,
+ min_width=1,
+ max_width=self.width,
+ encoding=self.encoding,
+ is_terminal=self.is_terminal,
+ )
+
+ @property
+ def size(self) -> ConsoleDimensions:
+ """Get the size of the console.
+
+ Returns:
+ ConsoleDimensions: A named tuple containing the dimensions.
+ """
+
+ if self._width is not None and self._height is not None:
+ return ConsoleDimensions(self._width - self.legacy_windows, self._height)
+
+ if self.is_dumb_terminal:
+ return ConsoleDimensions(80, 25)
+
+ width: Optional[int] = None
+ height: Optional[int] = None
+
+ if WINDOWS: # pragma: no cover
+ try:
+ width, height = os.get_terminal_size()
+ except (AttributeError, ValueError, OSError): # Probably not a terminal
+ pass
+ else:
+ for file_descriptor in _STD_STREAMS:
+ try:
+ width, height = os.get_terminal_size(file_descriptor)
+ except (AttributeError, ValueError, OSError):
+ pass
+ else:
+ break
+
+ columns = self._environ.get("COLUMNS")
+ if columns is not None and columns.isdigit():
+ width = int(columns)
+ lines = self._environ.get("LINES")
+ if lines is not None and lines.isdigit():
+ height = int(lines)
+
+ # get_terminal_size can report 0, 0 if run from pseudo-terminal
+ width = width or 80
+ height = height or 25
+ return ConsoleDimensions(
+ width - self.legacy_windows if self._width is None else self._width,
+ height if self._height is None else self._height,
+ )
+
+ @size.setter
+ def size(self, new_size: Tuple[int, int]) -> None:
+ """Set a new size for the terminal.
+
+ Args:
+ new_size (Tuple[int, int]): New width and height.
+ """
+ width, height = new_size
+ self._width = width
+ self._height = height
+
+ @property
+ def width(self) -> int:
+ """Get the width of the console.
+
+ Returns:
+ int: The width (in characters) of the console.
+ """
+ return self.size.width
+
+ @width.setter
+ def width(self, width: int) -> None:
+ """Set width.
+
+ Args:
+ width (int): New width.
+ """
+ self._width = width
+
+ @property
+ def height(self) -> int:
+ """Get the height of the console.
+
+ Returns:
+ int: The height (in lines) of the console.
+ """
+ return self.size.height
+
+ @height.setter
+ def height(self, height: int) -> None:
+ """Set height.
+
+ Args:
+ height (int): new height.
+ """
+ self._height = height
+
+ def bell(self) -> None:
+ """Play a 'bell' sound (if supported by the terminal)."""
+ self.control(Control.bell())
+
+ def capture(self) -> Capture:
+ """A context manager to *capture* the result of print() or log() in a string,
+ rather than writing it to the console.
+
+ Example:
+ >>> from rich.console import Console
+ >>> console = Console()
+ >>> with console.capture() as capture:
+ ... console.print("[bold magenta]Hello World[/]")
+ >>> print(capture.get())
+
+ Returns:
+ Capture: Context manager with disables writing to the terminal.
+ """
+ capture = Capture(self)
+ return capture
+
+ def pager(
+ self, pager: Optional[Pager] = None, styles: bool = False, links: bool = False
+ ) -> PagerContext:
+ """A context manager to display anything printed within a "pager". The pager application
+ is defined by the system and will typically support at least pressing a key to scroll.
+
+ Args:
+ pager (Pager, optional): A pager object, or None to use :class:`~rich.pager.SystemPager`. Defaults to None.
+ styles (bool, optional): Show styles in pager. Defaults to False.
+ links (bool, optional): Show links in pager. Defaults to False.
+
+ Example:
+ >>> from rich.console import Console
+ >>> from rich.__main__ import make_test_card
+ >>> console = Console()
+ >>> with console.pager():
+ console.print(make_test_card())
+
+ Returns:
+ PagerContext: A context manager.
+ """
+ return PagerContext(self, pager=pager, styles=styles, links=links)
+
+ def line(self, count: int = 1) -> None:
+ """Write new line(s).
+
+ Args:
+ count (int, optional): Number of new lines. Defaults to 1.
+ """
+
+ assert count >= 0, "count must be >= 0"
+ self.print(NewLine(count))
+
+ def clear(self, home: bool = True) -> None:
+ """Clear the screen.
+
+ Args:
+ home (bool, optional): Also move the cursor to 'home' position. Defaults to True.
+ """
+ if home:
+ self.control(Control.clear(), Control.home())
+ else:
+ self.control(Control.clear())
+
+ def status(
+ self,
+ status: RenderableType,
+ *,
+ spinner: str = "dots",
+ spinner_style: str = "status.spinner",
+ speed: float = 1.0,
+ refresh_per_second: float = 12.5,
+ ) -> "Status":
+ """Display a status and spinner.
+
+ Args:
+ status (RenderableType): A status renderable (str or Text typically).
+ spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots".
+ spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner".
+ speed (float, optional): Speed factor for spinner animation. Defaults to 1.0.
+ refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5.
+
+ Returns:
+ Status: A Status object that may be used as a context manager.
+ """
+ from .status import Status
+
+ status_renderable = Status(
+ status,
+ console=self,
+ spinner=spinner,
+ spinner_style=spinner_style,
+ speed=speed,
+ refresh_per_second=refresh_per_second,
+ )
+ return status_renderable
+
+ def show_cursor(self, show: bool = True) -> bool:
+ """Show or hide the cursor.
+
+ Args:
+ show (bool, optional): Set visibility of the cursor.
+ """
+ if self.is_terminal:
+ self.control(Control.show_cursor(show))
+ return True
+ return False
+
+ def set_alt_screen(self, enable: bool = True) -> bool:
+ """Enables alternative screen mode.
+
+ Note, if you enable this mode, you should ensure that is disabled before
+ the application exits. See :meth:`~rich.Console.screen` for a context manager
+ that handles this for you.
+
+ Args:
+ enable (bool, optional): Enable (True) or disable (False) alternate screen. Defaults to True.
+
+ Returns:
+ bool: True if the control codes were written.
+
+ """
+ changed = False
+ if self.is_terminal and not self.legacy_windows:
+ self.control(Control.alt_screen(enable))
+ changed = True
+ self._is_alt_screen = enable
+ return changed
+
+ @property
+ def is_alt_screen(self) -> bool:
+ """Check if the alt screen was enabled.
+
+ Returns:
+ bool: True if the alt screen was enabled, otherwise False.
+ """
+ return self._is_alt_screen
+
+ def set_window_title(self, title: str) -> bool:
+ """Set the title of the console terminal window.
+
+ Warning: There is no means within Rich of "resetting" the window title to its
+ previous value, meaning the title you set will persist even after your application
+ exits.
+
+ ``fish`` shell resets the window title before and after each command by default,
+ negating this issue. Windows Terminal and command prompt will also reset the title for you.
+ Most other shells and terminals, however, do not do this.
+
+ Some terminals may require configuration changes before you can set the title.
+ Some terminals may not support setting the title at all.
+
+ Other software (including the terminal itself, the shell, custom prompts, plugins, etc.)
+ may also set the terminal window title. This could result in whatever value you write
+ using this method being overwritten.
+
+ Args:
+ title (str): The new title of the terminal window.
+
+ Returns:
+ bool: True if the control code to change the terminal title was
+ written, otherwise False. Note that a return value of True
+ does not guarantee that the window title has actually changed,
+ since the feature may be unsupported/disabled in some terminals.
+ """
+ if self.is_terminal:
+ self.control(Control.title(title))
+ return True
+ return False
+
+ def screen(
+ self, hide_cursor: bool = True, style: Optional[StyleType] = None
+ ) -> "ScreenContext":
+ """Context manager to enable and disable 'alternative screen' mode.
+
+ Args:
+ hide_cursor (bool, optional): Also hide the cursor. Defaults to False.
+ style (Style, optional): Optional style for screen. Defaults to None.
+
+ Returns:
+ ~ScreenContext: Context which enables alternate screen on enter, and disables it on exit.
+ """
+ return ScreenContext(self, hide_cursor=hide_cursor, style=style or "")
+
+ def measure(
+ self, renderable: RenderableType, *, options: Optional[ConsoleOptions] = None
+ ) -> Measurement:
+ """Measure a renderable. Returns a :class:`~rich.measure.Measurement` object which contains
+ information regarding the number of characters required to print the renderable.
+
+ Args:
+ renderable (RenderableType): Any renderable or string.
+ options (Optional[ConsoleOptions], optional): Options to use when measuring, or None
+ to use default options. Defaults to None.
+
+ Returns:
+ Measurement: A measurement of the renderable.
+ """
+ measurement = Measurement.get(self, options or self.options, renderable)
+ return measurement
+
+ def render(
+ self, renderable: RenderableType, options: Optional[ConsoleOptions] = None
+ ) -> Iterable[Segment]:
+ """Render an object in to an iterable of `Segment` instances.
+
+ This method contains the logic for rendering objects with the console protocol.
+ You are unlikely to need to use it directly, unless you are extending the library.
+
+ Args:
+ renderable (RenderableType): An object supporting the console protocol, or
+ an object that may be converted to a string.
+ options (ConsoleOptions, optional): An options object, or None to use self.options. Defaults to None.
+
+ Returns:
+ Iterable[Segment]: An iterable of segments that may be rendered.
+ """
+
+ _options = options or self.options
+ if _options.max_width < 1:
+ # No space to render anything. This prevents potential recursion errors.
+ return
+ render_iterable: RenderResult
+
+ renderable = rich_cast(renderable)
+ if hasattr(renderable, "__rich_console__") and not isclass(renderable):
+ render_iterable = renderable.__rich_console__(self, _options) # type: ignore[union-attr]
+ elif isinstance(renderable, str):
+ text_renderable = self.render_str(
+ renderable, highlight=_options.highlight, markup=_options.markup
+ )
+ render_iterable = text_renderable.__rich_console__(self, _options)
+ else:
+ raise errors.NotRenderableError(
+ f"Unable to render {renderable!r}; "
+ "A str, Segment or object with __rich_console__ method is required"
+ )
+
+ try:
+ iter_render = iter(render_iterable)
+ except TypeError:
+ raise errors.NotRenderableError(
+ f"object {render_iterable!r} is not renderable"
+ )
+ _Segment = Segment
+ _options = _options.reset_height()
+ for render_output in iter_render:
+ if isinstance(render_output, _Segment):
+ yield render_output
+ else:
+ yield from self.render(render_output, _options)
+
+ def render_lines(
+ self,
+ renderable: RenderableType,
+ options: Optional[ConsoleOptions] = None,
+ *,
+ style: Optional[Style] = None,
+ pad: bool = True,
+ new_lines: bool = False,
+ ) -> List[List[Segment]]:
+ """Render objects in to a list of lines.
+
+ The output of render_lines is useful when further formatting of rendered console text
+ is required, such as the Panel class which draws a border around any renderable object.
+
+ Args:
+ renderable (RenderableType): Any object renderable in the console.
+ options (Optional[ConsoleOptions], optional): Console options, or None to use self.options. Default to ``None``.
+ style (Style, optional): Optional style to apply to renderables. Defaults to ``None``.
+ pad (bool, optional): Pad lines shorter than render width. Defaults to ``True``.
+ new_lines (bool, optional): Include "\n" characters at end of lines.
+
+ Returns:
+ List[List[Segment]]: A list of lines, where a line is a list of Segment objects.
+ """
+ with self._lock:
+ render_options = options or self.options
+ _rendered = self.render(renderable, render_options)
+ if style:
+ _rendered = Segment.apply_style(_rendered, style)
+
+ render_height = render_options.height
+ if render_height is not None:
+ render_height = max(0, render_height)
+
+ lines = list(
+ islice(
+ Segment.split_and_crop_lines(
+ _rendered,
+ render_options.max_width,
+ include_new_lines=new_lines,
+ pad=pad,
+ style=style,
+ ),
+ None,
+ render_height,
+ )
+ )
+ if render_options.height is not None:
+ extra_lines = render_options.height - len(lines)
+ if extra_lines > 0:
+ pad_line = [
+ [Segment(" " * render_options.max_width, style), Segment("\n")]
+ if new_lines
+ else [Segment(" " * render_options.max_width, style)]
+ ]
+ lines.extend(pad_line * extra_lines)
+
+ return lines
+
+ def render_str(
+ self,
+ text: str,
+ *,
+ style: Union[str, Style] = "",
+ justify: Optional[JustifyMethod] = None,
+ overflow: Optional[OverflowMethod] = None,
+ emoji: Optional[bool] = None,
+ markup: Optional[bool] = None,
+ highlight: Optional[bool] = None,
+ highlighter: Optional[HighlighterType] = None,
+ ) -> "Text":
+ """Convert a string to a Text instance. This is called automatically if
+ you print or log a string.
+
+ Args:
+ text (str): Text to render.
+ style (Union[str, Style], optional): Style to apply to rendered text.
+ justify (str, optional): Justify method: "default", "left", "center", "full", or "right". Defaults to ``None``.
+ overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to ``None``.
+ emoji (Optional[bool], optional): Enable emoji, or ``None`` to use Console default.
+ markup (Optional[bool], optional): Enable markup, or ``None`` to use Console default.
+ highlight (Optional[bool], optional): Enable highlighting, or ``None`` to use Console default.
+ highlighter (HighlighterType, optional): Optional highlighter to apply.
+ Returns:
+ ConsoleRenderable: Renderable object.
+
+ """
+ emoji_enabled = emoji or (emoji is None and self._emoji)
+ markup_enabled = markup or (markup is None and self._markup)
+ highlight_enabled = highlight or (highlight is None and self._highlight)
+
+ if markup_enabled:
+ rich_text = render_markup(
+ text,
+ style=style,
+ emoji=emoji_enabled,
+ emoji_variant=self._emoji_variant,
+ )
+ rich_text.justify = justify
+ rich_text.overflow = overflow
+ else:
+ rich_text = Text(
+ _emoji_replace(text, default_variant=self._emoji_variant)
+ if emoji_enabled
+ else text,
+ justify=justify,
+ overflow=overflow,
+ style=style,
+ )
+
+ _highlighter = (highlighter or self.highlighter) if highlight_enabled else None
+ if _highlighter is not None:
+ highlight_text = _highlighter(str(rich_text))
+ highlight_text.copy_styles(rich_text)
+ return highlight_text
+
+ return rich_text
+
+ def get_style(
+ self, name: Union[str, Style], *, default: Optional[Union[Style, str]] = None
+ ) -> Style:
+ """Get a Style instance by its theme name or parse a definition.
+
+ Args:
+ name (str): The name of a style or a style definition.
+
+ Returns:
+ Style: A Style object.
+
+ Raises:
+ MissingStyle: If no style could be parsed from name.
+
+ """
+ if isinstance(name, Style):
+ return name
+
+ try:
+ style = self._theme_stack.get(name)
+ if style is None:
+ style = Style.parse(name)
+ return style.copy() if style.link else style
+ except errors.StyleSyntaxError as error:
+ if default is not None:
+ return self.get_style(default)
+ raise errors.MissingStyle(
+ f"Failed to get style {name!r}; {error}"
+ ) from None
+
+ def _collect_renderables(
+ self,
+ objects: Iterable[Any],
+ sep: str,
+ end: str,
+ *,
+ justify: Optional[JustifyMethod] = None,
+ emoji: Optional[bool] = None,
+ markup: Optional[bool] = None,
+ highlight: Optional[bool] = None,
+ ) -> List[ConsoleRenderable]:
+ """Combine a number of renderables and text into one renderable.
+
+ Args:
+ objects (Iterable[Any]): Anything that Rich can render.
+ sep (str): String to write between print data.
+ end (str): String to write at end of print data.
+ justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``.
+ emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default.
+ markup (Optional[bool], optional): Enable markup, or ``None`` to use console default.
+ highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default.
+
+ Returns:
+ List[ConsoleRenderable]: A list of things to render.
+ """
+ renderables: List[ConsoleRenderable] = []
+ _append = renderables.append
+ text: List[Text] = []
+ append_text = text.append
+
+ append = _append
+ if justify in ("left", "center", "right"):
+
+ def align_append(renderable: RenderableType) -> None:
+ _append(Align(renderable, cast(AlignMethod, justify)))
+
+ append = align_append
+
+ _highlighter: HighlighterType = _null_highlighter
+ if highlight or (highlight is None and self._highlight):
+ _highlighter = self.highlighter
+
+ def check_text() -> None:
+ if text:
+ sep_text = Text(sep, justify=justify, end=end)
+ append(sep_text.join(text))
+ del text[:]
+
+ for renderable in objects:
+ renderable = rich_cast(renderable)
+ if isinstance(renderable, str):
+ append_text(
+ self.render_str(
+ renderable, emoji=emoji, markup=markup, highlighter=_highlighter
+ )
+ )
+ elif isinstance(renderable, Text):
+ append_text(renderable)
+ elif isinstance(renderable, ConsoleRenderable):
+ check_text()
+ append(renderable)
+ elif is_expandable(renderable):
+ check_text()
+ append(Pretty(renderable, highlighter=_highlighter))
+ else:
+ append_text(_highlighter(str(renderable)))
+
+ check_text()
+
+ if self.style is not None:
+ style = self.get_style(self.style)
+ renderables = [Styled(renderable, style) for renderable in renderables]
+
+ return renderables
+
+ def rule(
+ self,
+ title: TextType = "",
+ *,
+ characters: str = "─",
+ style: Union[str, Style] = "rule.line",
+ align: AlignMethod = "center",
+ ) -> None:
+ """Draw a line with optional centered title.
+
+ Args:
+ title (str, optional): Text to render over the rule. Defaults to "".
+ characters (str, optional): Character(s) to form the line. Defaults to "─".
+ style (str, optional): Style of line. Defaults to "rule.line".
+ align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center".
+ """
+ from .rule import Rule
+
+ rule = Rule(title=title, characters=characters, style=style, align=align)
+ self.print(rule)
+
+ def control(self, *control: Control) -> None:
+ """Insert non-printing control codes.
+
+ Args:
+ control_codes (str): Control codes, such as those that may move the cursor.
+ """
+ if not self.is_dumb_terminal:
+ with self:
+ self._buffer.extend(_control.segment for _control in control)
+
+ def out(
+ self,
+ *objects: Any,
+ sep: str = " ",
+ end: str = "\n",
+ style: Optional[Union[str, Style]] = None,
+ highlight: Optional[bool] = None,
+ ) -> None:
+ """Output to the terminal. This is a low-level way of writing to the terminal which unlike
+ :meth:`~rich.console.Console.print` won't pretty print, wrap text, or apply markup, but will
+ optionally apply highlighting and a basic style.
+
+ Args:
+ sep (str, optional): String to write between print data. Defaults to " ".
+ end (str, optional): String to write at end of print data. Defaults to "\\\\n".
+ style (Union[str, Style], optional): A style to apply to output. Defaults to None.
+ highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use
+ console default. Defaults to ``None``.
+ """
+ raw_output: str = sep.join(str(_object) for _object in objects)
+ self.print(
+ raw_output,
+ style=style,
+ highlight=highlight,
+ emoji=False,
+ markup=False,
+ no_wrap=True,
+ overflow="ignore",
+ crop=False,
+ end=end,
+ )
+
+ def print(
+ self,
+ *objects: Any,
+ sep: str = " ",
+ end: str = "\n",
+ style: Optional[Union[str, Style]] = None,
+ justify: Optional[JustifyMethod] = None,
+ overflow: Optional[OverflowMethod] = None,
+ no_wrap: Optional[bool] = None,
+ emoji: Optional[bool] = None,
+ markup: Optional[bool] = None,
+ highlight: Optional[bool] = None,
+ width: Optional[int] = None,
+ height: Optional[int] = None,
+ crop: bool = True,
+ soft_wrap: Optional[bool] = None,
+ new_line_start: bool = False,
+ ) -> None:
+ """Print to the console.
+
+ Args:
+ objects (positional args): Objects to log to the terminal.
+ sep (str, optional): String to write between print data. Defaults to " ".
+ end (str, optional): String to write at end of print data. Defaults to "\\\\n".
+ style (Union[str, Style], optional): A style to apply to output. Defaults to None.
+ justify (str, optional): Justify method: "default", "left", "right", "center", or "full". Defaults to ``None``.
+ overflow (str, optional): Overflow method: "ignore", "crop", "fold", or "ellipsis". Defaults to None.
+ no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to None.
+ emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to ``None``.
+ markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to ``None``.
+ highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to ``None``.
+ width (Optional[int], optional): Width of output, or ``None`` to auto-detect. Defaults to ``None``.
+ crop (Optional[bool], optional): Crop output to width of terminal. Defaults to True.
+ soft_wrap (bool, optional): Enable soft wrap mode which disables word wrapping and cropping of text or ``None`` for
+ Console default. Defaults to ``None``.
+ new_line_start (bool, False): Insert a new line at the start if the output contains more than one line. Defaults to ``False``.
+ """
+ if not objects:
+ objects = (NewLine(),)
+
+ if soft_wrap is None:
+ soft_wrap = self.soft_wrap
+ if soft_wrap:
+ if no_wrap is None:
+ no_wrap = True
+ if overflow is None:
+ overflow = "ignore"
+ crop = False
+ render_hooks = self._render_hooks[:]
+ with self:
+ renderables = self._collect_renderables(
+ objects,
+ sep,
+ end,
+ justify=justify,
+ emoji=emoji,
+ markup=markup,
+ highlight=highlight,
+ )
+ for hook in render_hooks:
+ renderables = hook.process_renderables(renderables)
+ render_options = self.options.update(
+ justify=justify,
+ overflow=overflow,
+ width=min(width, self.width) if width is not None else NO_CHANGE,
+ height=height,
+ no_wrap=no_wrap,
+ markup=markup,
+ highlight=highlight,
+ )
+
+ new_segments: List[Segment] = []
+ extend = new_segments.extend
+ render = self.render
+ if style is None:
+ for renderable in renderables:
+ extend(render(renderable, render_options))
+ else:
+ for renderable in renderables:
+ extend(
+ Segment.apply_style(
+ render(renderable, render_options), self.get_style(style)
+ )
+ )
+ if new_line_start:
+ if (
+ len("".join(segment.text for segment in new_segments).splitlines())
+ > 1
+ ):
+ new_segments.insert(0, Segment.line())
+ if crop:
+ buffer_extend = self._buffer.extend
+ for line in Segment.split_and_crop_lines(
+ new_segments, self.width, pad=False
+ ):
+ buffer_extend(line)
+ else:
+ self._buffer.extend(new_segments)
+
+ def print_json(
+ self,
+ json: Optional[str] = None,
+ *,
+ data: Any = None,
+ indent: Union[None, int, str] = 2,
+ highlight: bool = True,
+ skip_keys: bool = False,
+ ensure_ascii: bool = False,
+ check_circular: bool = True,
+ allow_nan: bool = True,
+ default: Optional[Callable[[Any], Any]] = None,
+ sort_keys: bool = False,
+ ) -> None:
+ """Pretty prints JSON. Output will be valid JSON.
+
+ Args:
+ json (Optional[str]): A string containing JSON.
+ data (Any): If json is not supplied, then encode this data.
+ indent (Union[None, int, str], optional): Number of spaces to indent. Defaults to 2.
+ highlight (bool, optional): Enable highlighting of output: Defaults to True.
+ skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False.
+ ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False.
+ check_circular (bool, optional): Check for circular references. Defaults to True.
+ allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True.
+ default (Callable, optional): A callable that converts values that can not be encoded
+ in to something that can be JSON encoded. Defaults to None.
+ sort_keys (bool, optional): Sort dictionary keys. Defaults to False.
+ """
+ from pip._vendor.rich.json import JSON
+
+ if json is None:
+ json_renderable = JSON.from_data(
+ data,
+ indent=indent,
+ highlight=highlight,
+ skip_keys=skip_keys,
+ ensure_ascii=ensure_ascii,
+ check_circular=check_circular,
+ allow_nan=allow_nan,
+ default=default,
+ sort_keys=sort_keys,
+ )
+ else:
+ if not isinstance(json, str):
+ raise TypeError(
+ f"json must be str. Did you mean print_json(data={json!r}) ?"
+ )
+ json_renderable = JSON(
+ json,
+ indent=indent,
+ highlight=highlight,
+ skip_keys=skip_keys,
+ ensure_ascii=ensure_ascii,
+ check_circular=check_circular,
+ allow_nan=allow_nan,
+ default=default,
+ sort_keys=sort_keys,
+ )
+ self.print(json_renderable, soft_wrap=True)
+
+ def update_screen(
+ self,
+ renderable: RenderableType,
+ *,
+ region: Optional[Region] = None,
+ options: Optional[ConsoleOptions] = None,
+ ) -> None:
+ """Update the screen at a given offset.
+
+ Args:
+ renderable (RenderableType): A Rich renderable.
+ region (Region, optional): Region of screen to update, or None for entire screen. Defaults to None.
+ x (int, optional): x offset. Defaults to 0.
+ y (int, optional): y offset. Defaults to 0.
+
+ Raises:
+ errors.NoAltScreen: If the Console isn't in alt screen mode.
+
+ """
+ if not self.is_alt_screen:
+ raise errors.NoAltScreen("Alt screen must be enabled to call update_screen")
+ render_options = options or self.options
+ if region is None:
+ x = y = 0
+ render_options = render_options.update_dimensions(
+ render_options.max_width, render_options.height or self.height
+ )
+ else:
+ x, y, width, height = region
+ render_options = render_options.update_dimensions(width, height)
+
+ lines = self.render_lines(renderable, options=render_options)
+ self.update_screen_lines(lines, x, y)
+
+ def update_screen_lines(
+ self, lines: List[List[Segment]], x: int = 0, y: int = 0
+ ) -> None:
+ """Update lines of the screen at a given offset.
+
+ Args:
+ lines (List[List[Segment]]): Rendered lines (as produced by :meth:`~rich.Console.render_lines`).
+ x (int, optional): x offset (column no). Defaults to 0.
+ y (int, optional): y offset (column no). Defaults to 0.
+
+ Raises:
+ errors.NoAltScreen: If the Console isn't in alt screen mode.
+ """
+ if not self.is_alt_screen:
+ raise errors.NoAltScreen("Alt screen must be enabled to call update_screen")
+ screen_update = ScreenUpdate(lines, x, y)
+ segments = self.render(screen_update)
+ self._buffer.extend(segments)
+ self._check_buffer()
+
+ def print_exception(
+ self,
+ *,
+ width: Optional[int] = 100,
+ extra_lines: int = 3,
+ theme: Optional[str] = None,
+ word_wrap: bool = False,
+ show_locals: bool = False,
+ suppress: Iterable[Union[str, ModuleType]] = (),
+ max_frames: int = 100,
+ ) -> None:
+ """Prints a rich render of the last exception and traceback.
+
+ Args:
+ width (Optional[int], optional): Number of characters used to render code. Defaults to 100.
+ extra_lines (int, optional): Additional lines of code to render. Defaults to 3.
+ theme (str, optional): Override pygments theme used in traceback
+ word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
+ show_locals (bool, optional): Enable display of local variables. Defaults to False.
+ suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
+ max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
+ """
+ from .traceback import Traceback
+
+ traceback = Traceback(
+ width=width,
+ extra_lines=extra_lines,
+ theme=theme,
+ word_wrap=word_wrap,
+ show_locals=show_locals,
+ suppress=suppress,
+ max_frames=max_frames,
+ )
+ self.print(traceback)
+
+ @staticmethod
+ def _caller_frame_info(
+ offset: int,
+ currentframe: Callable[[], Optional[FrameType]] = inspect.currentframe,
+ ) -> Tuple[str, int, Dict[str, Any]]:
+ """Get caller frame information.
+
+ Args:
+ offset (int): the caller offset within the current frame stack.
+ currentframe (Callable[[], Optional[FrameType]], optional): the callable to use to
+ retrieve the current frame. Defaults to ``inspect.currentframe``.
+
+ Returns:
+ Tuple[str, int, Dict[str, Any]]: A tuple containing the filename, the line number and
+ the dictionary of local variables associated with the caller frame.
+
+ Raises:
+ RuntimeError: If the stack offset is invalid.
+ """
+ # Ignore the frame of this local helper
+ offset += 1
+
+ frame = currentframe()
+ if frame is not None:
+ # Use the faster currentframe where implemented
+ while offset and frame is not None:
+ frame = frame.f_back
+ offset -= 1
+ assert frame is not None
+ return frame.f_code.co_filename, frame.f_lineno, frame.f_locals
+ else:
+ # Fallback to the slower stack
+ frame_info = inspect.stack()[offset]
+ return frame_info.filename, frame_info.lineno, frame_info.frame.f_locals
+
+ def log(
+ self,
+ *objects: Any,
+ sep: str = " ",
+ end: str = "\n",
+ style: Optional[Union[str, Style]] = None,
+ justify: Optional[JustifyMethod] = None,
+ emoji: Optional[bool] = None,
+ markup: Optional[bool] = None,
+ highlight: Optional[bool] = None,
+ log_locals: bool = False,
+ _stack_offset: int = 1,
+ ) -> None:
+ """Log rich content to the terminal.
+
+ Args:
+ objects (positional args): Objects to log to the terminal.
+ sep (str, optional): String to write between print data. Defaults to " ".
+ end (str, optional): String to write at end of print data. Defaults to "\\\\n".
+ style (Union[str, Style], optional): A style to apply to output. Defaults to None.
+ justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``.
+ overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None.
+ emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to None.
+ markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to None.
+ highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to None.
+ log_locals (bool, optional): Boolean to enable logging of locals where ``log()``
+ was called. Defaults to False.
+ _stack_offset (int, optional): Offset of caller from end of call stack. Defaults to 1.
+ """
+ if not objects:
+ objects = (NewLine(),)
+
+ render_hooks = self._render_hooks[:]
+
+ with self:
+ renderables = self._collect_renderables(
+ objects,
+ sep,
+ end,
+ justify=justify,
+ emoji=emoji,
+ markup=markup,
+ highlight=highlight,
+ )
+ if style is not None:
+ renderables = [Styled(renderable, style) for renderable in renderables]
+
+ filename, line_no, locals = self._caller_frame_info(_stack_offset)
+ link_path = None if filename.startswith("<") else os.path.abspath(filename)
+ path = filename.rpartition(os.sep)[-1]
+ if log_locals:
+ locals_map = {
+ key: value
+ for key, value in locals.items()
+ if not key.startswith("__")
+ }
+ renderables.append(render_scope(locals_map, title="[i]locals"))
+
+ renderables = [
+ self._log_render(
+ self,
+ renderables,
+ log_time=self.get_datetime(),
+ path=path,
+ line_no=line_no,
+ link_path=link_path,
+ )
+ ]
+ for hook in render_hooks:
+ renderables = hook.process_renderables(renderables)
+ new_segments: List[Segment] = []
+ extend = new_segments.extend
+ render = self.render
+ render_options = self.options
+ for renderable in renderables:
+ extend(render(renderable, render_options))
+ buffer_extend = self._buffer.extend
+ for line in Segment.split_and_crop_lines(
+ new_segments, self.width, pad=False
+ ):
+ buffer_extend(line)
+
+ def _check_buffer(self) -> None:
+ """Check if the buffer may be rendered. Render it if it can (e.g. Console.quiet is False)
+ Rendering is supported on Windows, Unix and Jupyter environments. For
+ legacy Windows consoles, the win32 API is called directly.
+ This method will also record what it renders if recording is enabled via Console.record.
+ """
+ if self.quiet:
+ del self._buffer[:]
+ return
+ with self._lock:
+ if self.record:
+ with self._record_buffer_lock:
+ self._record_buffer.extend(self._buffer[:])
+
+ if self._buffer_index == 0:
+
+ if self.is_jupyter: # pragma: no cover
+ from .jupyter import display
+
+ display(self._buffer, self._render_buffer(self._buffer[:]))
+ del self._buffer[:]
+ else:
+ if WINDOWS:
+ use_legacy_windows_render = False
+ if self.legacy_windows:
+ try:
+ use_legacy_windows_render = (
+ self.file.fileno() in _STD_STREAMS_OUTPUT
+ )
+ except (ValueError, io.UnsupportedOperation):
+ pass
+
+ if use_legacy_windows_render:
+ from pip._vendor.rich._win32_console import LegacyWindowsTerm
+ from pip._vendor.rich._windows_renderer import legacy_windows_render
+
+ buffer = self._buffer[:]
+ if self.no_color and self._color_system:
+ buffer = list(Segment.remove_color(buffer))
+
+ legacy_windows_render(buffer, LegacyWindowsTerm(self.file))
+ else:
+ # Either a non-std stream on legacy Windows, or modern Windows.
+ text = self._render_buffer(self._buffer[:])
+ # https://bugs.python.org/issue37871
+ write = self.file.write
+ for line in text.splitlines(True):
+ try:
+ write(line)
+ except UnicodeEncodeError as error:
+ error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***"
+ raise
+ else:
+ text = self._render_buffer(self._buffer[:])
+ try:
+ self.file.write(text)
+ except UnicodeEncodeError as error:
+ error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***"
+ raise
+
+ self.file.flush()
+ del self._buffer[:]
+
+ def _render_buffer(self, buffer: Iterable[Segment]) -> str:
+ """Render buffered output, and clear buffer."""
+ output: List[str] = []
+ append = output.append
+ color_system = self._color_system
+ legacy_windows = self.legacy_windows
+ not_terminal = not self.is_terminal
+ if self.no_color and color_system:
+ buffer = Segment.remove_color(buffer)
+ for text, style, control in buffer:
+ if style:
+ append(
+ style.render(
+ text,
+ color_system=color_system,
+ legacy_windows=legacy_windows,
+ )
+ )
+ elif not (not_terminal and control):
+ append(text)
+
+ rendered = "".join(output)
+ return rendered
+
+ def input(
+ self,
+ prompt: TextType = "",
+ *,
+ markup: bool = True,
+ emoji: bool = True,
+ password: bool = False,
+ stream: Optional[TextIO] = None,
+ ) -> str:
+ """Displays a prompt and waits for input from the user. The prompt may contain color / style.
+
+ It works in the same way as Python's builtin :func:`input` function and provides elaborate line editing and history features if Python's builtin :mod:`readline` module is previously loaded.
+
+ Args:
+ prompt (Union[str, Text]): Text to render in the prompt.
+ markup (bool, optional): Enable console markup (requires a str prompt). Defaults to True.
+ emoji (bool, optional): Enable emoji (requires a str prompt). Defaults to True.
+ password: (bool, optional): Hide typed text. Defaults to False.
+ stream: (TextIO, optional): Optional file to read input from (rather than stdin). Defaults to None.
+
+ Returns:
+ str: Text read from stdin.
+ """
+ if prompt:
+ self.print(prompt, markup=markup, emoji=emoji, end="")
+ if password:
+ result = getpass("", stream=stream)
+ else:
+ if stream:
+ result = stream.readline()
+ else:
+ result = input()
+ return result
+
+ def export_text(self, *, clear: bool = True, styles: bool = False) -> str:
+ """Generate text from console contents (requires record=True argument in constructor).
+
+ Args:
+ clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``.
+ styles (bool, optional): If ``True``, ansi escape codes will be included. ``False`` for plain text.
+ Defaults to ``False``.
+
+ Returns:
+ str: String containing console contents.
+
+ """
+ assert (
+ self.record
+ ), "To export console contents set record=True in the constructor or instance"
+
+ with self._record_buffer_lock:
+ if styles:
+ text = "".join(
+ (style.render(text) if style else text)
+ for text, style, _ in self._record_buffer
+ )
+ else:
+ text = "".join(
+ segment.text
+ for segment in self._record_buffer
+ if not segment.control
+ )
+ if clear:
+ del self._record_buffer[:]
+ return text
+
+ def save_text(self, path: str, *, clear: bool = True, styles: bool = False) -> None:
+ """Generate text from console and save to a given location (requires record=True argument in constructor).
+
+ Args:
+ path (str): Path to write text files.
+ clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``.
+ styles (bool, optional): If ``True``, ansi style codes will be included. ``False`` for plain text.
+ Defaults to ``False``.
+
+ """
+ text = self.export_text(clear=clear, styles=styles)
+ with open(path, "wt", encoding="utf-8") as write_file:
+ write_file.write(text)
+
+ def export_html(
+ self,
+ *,
+ theme: Optional[TerminalTheme] = None,
+ clear: bool = True,
+ code_format: Optional[str] = None,
+ inline_styles: bool = False,
+ ) -> str:
+ """Generate HTML from console contents (requires record=True argument in constructor).
+
+ Args:
+ theme (TerminalTheme, optional): TerminalTheme object containing console colors.
+ clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``.
+ code_format (str, optional): Format string to render HTML. In addition to '{foreground}',
+ '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``.
+ inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files
+ larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag.
+ Defaults to False.
+
+ Returns:
+ str: String containing console contents as HTML.
+ """
+ assert (
+ self.record
+ ), "To export console contents set record=True in the constructor or instance"
+ fragments: List[str] = []
+ append = fragments.append
+ _theme = theme or DEFAULT_TERMINAL_THEME
+ stylesheet = ""
+
+ render_code_format = CONSOLE_HTML_FORMAT if code_format is None else code_format
+
+ with self._record_buffer_lock:
+ if inline_styles:
+ for text, style, _ in Segment.filter_control(
+ Segment.simplify(self._record_buffer)
+ ):
+ text = escape(text)
+ if style:
+ rule = style.get_html_style(_theme)
+ if style.link:
+ text = f'<a href="{style.link}">{text}</a>'
+ text = f'<span style="{rule}">{text}</span>' if rule else text
+ append(text)
+ else:
+ styles: Dict[str, int] = {}
+ for text, style, _ in Segment.filter_control(
+ Segment.simplify(self._record_buffer)
+ ):
+ text = escape(text)
+ if style:
+ rule = style.get_html_style(_theme)
+ style_number = styles.setdefault(rule, len(styles) + 1)
+ if style.link:
+ text = f'<a class="r{style_number}" href="{style.link}">{text}</a>'
+ else:
+ text = f'<span class="r{style_number}">{text}</span>'
+ append(text)
+ stylesheet_rules: List[str] = []
+ stylesheet_append = stylesheet_rules.append
+ for style_rule, style_number in styles.items():
+ if style_rule:
+ stylesheet_append(f".r{style_number} {{{style_rule}}}")
+ stylesheet = "\n".join(stylesheet_rules)
+
+ rendered_code = render_code_format.format(
+ code="".join(fragments),
+ stylesheet=stylesheet,
+ foreground=_theme.foreground_color.hex,
+ background=_theme.background_color.hex,
+ )
+ if clear:
+ del self._record_buffer[:]
+ return rendered_code
+
+ def save_html(
+ self,
+ path: str,
+ *,
+ theme: Optional[TerminalTheme] = None,
+ clear: bool = True,
+ code_format: str = CONSOLE_HTML_FORMAT,
+ inline_styles: bool = False,
+ ) -> None:
+ """Generate HTML from console contents and write to a file (requires record=True argument in constructor).
+
+ Args:
+ path (str): Path to write html file.
+ theme (TerminalTheme, optional): TerminalTheme object containing console colors.
+ clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``.
+ code_format (str, optional): Format string to render HTML. In addition to '{foreground}',
+ '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``.
+ inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files
+ larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag.
+ Defaults to False.
+
+ """
+ html = self.export_html(
+ theme=theme,
+ clear=clear,
+ code_format=code_format,
+ inline_styles=inline_styles,
+ )
+ with open(path, "wt", encoding="utf-8") as write_file:
+ write_file.write(html)
+
+ def export_svg(
+ self,
+ *,
+ title: str = "Rich",
+ theme: Optional[TerminalTheme] = None,
+ clear: bool = True,
+ code_format: str = CONSOLE_SVG_FORMAT,
+ font_aspect_ratio: float = 0.61,
+ unique_id: Optional[str] = None,
+ ) -> str:
+ """
+ Generate an SVG from the console contents (requires record=True in Console constructor).
+
+ Args:
+ title (str, optional): The title of the tab in the output image
+ theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal
+ clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``
+ code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables
+ into the string in order to form the final SVG output. The default template used and the variables
+ injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable.
+ font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format``
+ string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font).
+ If you aren't specifying a different font inside ``code_format``, you probably don't need this.
+ unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node
+ ids). If not set, this defaults to a computed value based on the recorded content.
+ """
+
+ from pip._vendor.rich.cells import cell_len
+
+ style_cache: Dict[Style, str] = {}
+
+ def get_svg_style(style: Style) -> str:
+ """Convert a Style to CSS rules for SVG."""
+ if style in style_cache:
+ return style_cache[style]
+ css_rules = []
+ color = (
+ _theme.foreground_color
+ if (style.color is None or style.color.is_default)
+ else style.color.get_truecolor(_theme)
+ )
+ bgcolor = (
+ _theme.background_color
+ if (style.bgcolor is None or style.bgcolor.is_default)
+ else style.bgcolor.get_truecolor(_theme)
+ )
+ if style.reverse:
+ color, bgcolor = bgcolor, color
+ if style.dim:
+ color = blend_rgb(color, bgcolor, 0.4)
+ css_rules.append(f"fill: {color.hex}")
+ if style.bold:
+ css_rules.append("font-weight: bold")
+ if style.italic:
+ css_rules.append("font-style: italic;")
+ if style.underline:
+ css_rules.append("text-decoration: underline;")
+ if style.strike:
+ css_rules.append("text-decoration: line-through;")
+
+ css = ";".join(css_rules)
+ style_cache[style] = css
+ return css
+
+ _theme = theme or SVG_EXPORT_THEME
+
+ width = self.width
+ char_height = 20
+ char_width = char_height * font_aspect_ratio
+ line_height = char_height * 1.22
+
+ margin_top = 1
+ margin_right = 1
+ margin_bottom = 1
+ margin_left = 1
+
+ padding_top = 40
+ padding_right = 8
+ padding_bottom = 8
+ padding_left = 8
+
+ padding_width = padding_left + padding_right
+ padding_height = padding_top + padding_bottom
+ margin_width = margin_left + margin_right
+ margin_height = margin_top + margin_bottom
+
+ text_backgrounds: List[str] = []
+ text_group: List[str] = []
+ classes: Dict[str, int] = {}
+ style_no = 1
+
+ def escape_text(text: str) -> str:
+ """HTML escape text and replace spaces with nbsp."""
+ return escape(text).replace(" ", "&#160;")
+
+ def make_tag(
+ name: str, content: Optional[str] = None, **attribs: object
+ ) -> str:
+ """Make a tag from name, content, and attributes."""
+
+ def stringify(value: object) -> str:
+ if isinstance(value, (float)):
+ return format(value, "g")
+ return str(value)
+
+ tag_attribs = " ".join(
+ f'{k.lstrip("_").replace("_", "-")}="{stringify(v)}"'
+ for k, v in attribs.items()
+ )
+ return (
+ f"<{name} {tag_attribs}>{content}</{name}>"
+ if content
+ else f"<{name} {tag_attribs}/>"
+ )
+
+ with self._record_buffer_lock:
+ segments = list(Segment.filter_control(self._record_buffer))
+ if clear:
+ self._record_buffer.clear()
+
+ if unique_id is None:
+ unique_id = "terminal-" + str(
+ zlib.adler32(
+ ("".join(repr(segment) for segment in segments)).encode(
+ "utf-8",
+ "ignore",
+ )
+ + title.encode("utf-8", "ignore")
+ )
+ )
+ y = 0
+ for y, line in enumerate(Segment.split_and_crop_lines(segments, length=width)):
+ x = 0
+ for text, style, _control in line:
+ style = style or Style()
+ rules = get_svg_style(style)
+ if rules not in classes:
+ classes[rules] = style_no
+ style_no += 1
+ class_name = f"r{classes[rules]}"
+
+ if style.reverse:
+ has_background = True
+ background = (
+ _theme.foreground_color.hex
+ if style.color is None
+ else style.color.get_truecolor(_theme).hex
+ )
+ else:
+ bgcolor = style.bgcolor
+ has_background = bgcolor is not None and not bgcolor.is_default
+ background = (
+ _theme.background_color.hex
+ if style.bgcolor is None
+ else style.bgcolor.get_truecolor(_theme).hex
+ )
+
+ text_length = cell_len(text)
+ if has_background:
+ text_backgrounds.append(
+ make_tag(
+ "rect",
+ fill=background,
+ x=x * char_width,
+ y=y * line_height + 1.5,
+ width=char_width * text_length,
+ height=line_height + 0.25,
+ shape_rendering="crispEdges",
+ )
+ )
+
+ if text != " " * len(text):
+ text_group.append(
+ make_tag(
+ "text",
+ escape_text(text),
+ _class=f"{unique_id}-{class_name}",
+ x=x * char_width,
+ y=y * line_height + char_height,
+ textLength=char_width * len(text),
+ clip_path=f"url(#{unique_id}-line-{y})",
+ )
+ )
+ x += cell_len(text)
+
+ line_offsets = [line_no * line_height + 1.5 for line_no in range(y)]
+ lines = "\n".join(
+ f"""<clipPath id="{unique_id}-line-{line_no}">
+ {make_tag("rect", x=0, y=offset, width=char_width * width, height=line_height + 0.25)}
+ </clipPath>"""
+ for line_no, offset in enumerate(line_offsets)
+ )
+
+ styles = "\n".join(
+ f".{unique_id}-r{rule_no} {{ {css} }}" for css, rule_no in classes.items()
+ )
+ backgrounds = "".join(text_backgrounds)
+ matrix = "".join(text_group)
+
+ terminal_width = ceil(width * char_width + padding_width)
+ terminal_height = (y + 1) * line_height + padding_height
+ chrome = make_tag(
+ "rect",
+ fill=_theme.background_color.hex,
+ stroke="rgba(255,255,255,0.35)",
+ stroke_width="1",
+ x=margin_left,
+ y=margin_top,
+ width=terminal_width,
+ height=terminal_height,
+ rx=8,
+ )
+
+ title_color = _theme.foreground_color.hex
+ if title:
+ chrome += make_tag(
+ "text",
+ escape_text(title),
+ _class=f"{unique_id}-title",
+ fill=title_color,
+ text_anchor="middle",
+ x=terminal_width // 2,
+ y=margin_top + char_height + 6,
+ )
+ chrome += f"""
+ <g transform="translate(26,22)">
+ <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
+ <circle cx="22" cy="0" r="7" fill="#febc2e"/>
+ <circle cx="44" cy="0" r="7" fill="#28c840"/>
+ </g>
+ """
+
+ svg = code_format.format(
+ unique_id=unique_id,
+ char_width=char_width,
+ char_height=char_height,
+ line_height=line_height,
+ terminal_width=char_width * width - 1,
+ terminal_height=(y + 1) * line_height - 1,
+ width=terminal_width + margin_width,
+ height=terminal_height + margin_height,
+ terminal_x=margin_left + padding_left,
+ terminal_y=margin_top + padding_top,
+ styles=styles,
+ chrome=chrome,
+ backgrounds=backgrounds,
+ matrix=matrix,
+ lines=lines,
+ )
+ return svg
+
+ def save_svg(
+ self,
+ path: str,
+ *,
+ title: str = "Rich",
+ theme: Optional[TerminalTheme] = None,
+ clear: bool = True,
+ code_format: str = CONSOLE_SVG_FORMAT,
+ font_aspect_ratio: float = 0.61,
+ unique_id: Optional[str] = None,
+ ) -> None:
+ """Generate an SVG file from the console contents (requires record=True in Console constructor).
+
+ Args:
+ path (str): The path to write the SVG to.
+ title (str, optional): The title of the tab in the output image
+ theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal
+ clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``
+ code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables
+ into the string in order to form the final SVG output. The default template used and the variables
+ injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable.
+ font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format``
+ string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font).
+ If you aren't specifying a different font inside ``code_format``, you probably don't need this.
+ unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node
+ ids). If not set, this defaults to a computed value based on the recorded content.
+ """
+ svg = self.export_svg(
+ title=title,
+ theme=theme,
+ clear=clear,
+ code_format=code_format,
+ font_aspect_ratio=font_aspect_ratio,
+ unique_id=unique_id,
+ )
+ with open(path, "wt", encoding="utf-8") as write_file:
+ write_file.write(svg)
+
+
+def _svg_hash(svg_main_code: str) -> str:
+ """Returns a unique hash for the given SVG main code.
+
+ Args:
+ svg_main_code (str): The content we're going to inject in the SVG envelope.
+
+ Returns:
+ str: a hash of the given content
+ """
+ return str(zlib.adler32(svg_main_code.encode()))
+
+
+if __name__ == "__main__": # pragma: no cover
+ console = Console(record=True)
+
+ console.log(
+ "JSONRPC [i]request[/i]",
+ 5,
+ 1.3,
+ True,
+ False,
+ None,
+ {
+ "jsonrpc": "2.0",
+ "method": "subtract",
+ "params": {"minuend": 42, "subtrahend": 23},
+ "id": 3,
+ },
+ )
+
+ console.log("Hello, World!", "{'a': 1}", repr(console))
+
+ console.print(
+ {
+ "name": None,
+ "empty": [],
+ "quiz": {
+ "sport": {
+ "answered": True,
+ "q1": {
+ "question": "Which one is correct team name in NBA?",
+ "options": [
+ "New York Bulls",
+ "Los Angeles Kings",
+ "Golden State Warriors",
+ "Huston Rocket",
+ ],
+ "answer": "Huston Rocket",
+ },
+ },
+ "maths": {
+ "answered": False,
+ "q1": {
+ "question": "5 + 7 = ?",
+ "options": [10, 11, 12, 13],
+ "answer": 12,
+ },
+ "q2": {
+ "question": "12 - 8 = ?",
+ "options": [1, 2, 3, 4],
+ "answer": 4,
+ },
+ },
+ },
+ }
+ )
diff --git a/src/pip/_vendor/rich/constrain.py b/src/pip/_vendor/rich/constrain.py
new file mode 100644
index 000000000..65fdf5634
--- /dev/null
+++ b/src/pip/_vendor/rich/constrain.py
@@ -0,0 +1,37 @@
+from typing import Optional, TYPE_CHECKING
+
+from .jupyter import JupyterMixin
+from .measure import Measurement
+
+if TYPE_CHECKING:
+ from .console import Console, ConsoleOptions, RenderableType, RenderResult
+
+
+class Constrain(JupyterMixin):
+ """Constrain the width of a renderable to a given number of characters.
+
+ Args:
+ renderable (RenderableType): A renderable object.
+ width (int, optional): The maximum width (in characters) to render. Defaults to 80.
+ """
+
+ def __init__(self, renderable: "RenderableType", width: Optional[int] = 80) -> None:
+ self.renderable = renderable
+ self.width = width
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ if self.width is None:
+ yield self.renderable
+ else:
+ child_options = options.update_width(min(self.width, options.max_width))
+ yield from console.render(self.renderable, child_options)
+
+ def __rich_measure__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "Measurement":
+ if self.width is not None:
+ options = options.update_width(self.width)
+ measurement = Measurement.get(console, options, self.renderable)
+ return measurement
diff --git a/src/pip/_vendor/rich/containers.py b/src/pip/_vendor/rich/containers.py
new file mode 100644
index 000000000..e29cf3689
--- /dev/null
+++ b/src/pip/_vendor/rich/containers.py
@@ -0,0 +1,167 @@
+from itertools import zip_longest
+from typing import (
+ Iterator,
+ Iterable,
+ List,
+ Optional,
+ Union,
+ overload,
+ TypeVar,
+ TYPE_CHECKING,
+)
+
+if TYPE_CHECKING:
+ from .console import (
+ Console,
+ ConsoleOptions,
+ JustifyMethod,
+ OverflowMethod,
+ RenderResult,
+ RenderableType,
+ )
+ from .text import Text
+
+from .cells import cell_len
+from .measure import Measurement
+
+T = TypeVar("T")
+
+
+class Renderables:
+ """A list subclass which renders its contents to the console."""
+
+ def __init__(
+ self, renderables: Optional[Iterable["RenderableType"]] = None
+ ) -> None:
+ self._renderables: List["RenderableType"] = (
+ list(renderables) if renderables is not None else []
+ )
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ """Console render method to insert line-breaks."""
+ yield from self._renderables
+
+ def __rich_measure__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "Measurement":
+ dimensions = [
+ Measurement.get(console, options, renderable)
+ for renderable in self._renderables
+ ]
+ if not dimensions:
+ return Measurement(1, 1)
+ _min = max(dimension.minimum for dimension in dimensions)
+ _max = max(dimension.maximum for dimension in dimensions)
+ return Measurement(_min, _max)
+
+ def append(self, renderable: "RenderableType") -> None:
+ self._renderables.append(renderable)
+
+ def __iter__(self) -> Iterable["RenderableType"]:
+ return iter(self._renderables)
+
+
+class Lines:
+ """A list subclass which can render to the console."""
+
+ def __init__(self, lines: Iterable["Text"] = ()) -> None:
+ self._lines: List["Text"] = list(lines)
+
+ def __repr__(self) -> str:
+ return f"Lines({self._lines!r})"
+
+ def __iter__(self) -> Iterator["Text"]:
+ return iter(self._lines)
+
+ @overload
+ def __getitem__(self, index: int) -> "Text":
+ ...
+
+ @overload
+ def __getitem__(self, index: slice) -> List["Text"]:
+ ...
+
+ def __getitem__(self, index: Union[slice, int]) -> Union["Text", List["Text"]]:
+ return self._lines[index]
+
+ def __setitem__(self, index: int, value: "Text") -> "Lines":
+ self._lines[index] = value
+ return self
+
+ def __len__(self) -> int:
+ return self._lines.__len__()
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ """Console render method to insert line-breaks."""
+ yield from self._lines
+
+ def append(self, line: "Text") -> None:
+ self._lines.append(line)
+
+ def extend(self, lines: Iterable["Text"]) -> None:
+ self._lines.extend(lines)
+
+ def pop(self, index: int = -1) -> "Text":
+ return self._lines.pop(index)
+
+ def justify(
+ self,
+ console: "Console",
+ width: int,
+ justify: "JustifyMethod" = "left",
+ overflow: "OverflowMethod" = "fold",
+ ) -> None:
+ """Justify and overflow text to a given width.
+
+ Args:
+ console (Console): Console instance.
+ width (int): Number of characters per line.
+ justify (str, optional): Default justify method for text: "left", "center", "full" or "right". Defaults to "left".
+ overflow (str, optional): Default overflow for text: "crop", "fold", or "ellipsis". Defaults to "fold".
+
+ """
+ from .text import Text
+
+ if justify == "left":
+ for line in self._lines:
+ line.truncate(width, overflow=overflow, pad=True)
+ elif justify == "center":
+ for line in self._lines:
+ line.rstrip()
+ line.truncate(width, overflow=overflow)
+ line.pad_left((width - cell_len(line.plain)) // 2)
+ line.pad_right(width - cell_len(line.plain))
+ elif justify == "right":
+ for line in self._lines:
+ line.rstrip()
+ line.truncate(width, overflow=overflow)
+ line.pad_left(width - cell_len(line.plain))
+ elif justify == "full":
+ for line_index, line in enumerate(self._lines):
+ if line_index == len(self._lines) - 1:
+ break
+ words = line.split(" ")
+ words_size = sum(cell_len(word.plain) for word in words)
+ num_spaces = len(words) - 1
+ spaces = [1 for _ in range(num_spaces)]
+ index = 0
+ if spaces:
+ while words_size + num_spaces < width:
+ spaces[len(spaces) - index - 1] += 1
+ num_spaces += 1
+ index = (index + 1) % len(spaces)
+ tokens: List[Text] = []
+ for index, (word, next_word) in enumerate(
+ zip_longest(words, words[1:])
+ ):
+ tokens.append(word)
+ if index < len(spaces):
+ style = word.get_style_at_offset(console, -1)
+ next_style = next_word.get_style_at_offset(console, 0)
+ space_style = style if style == next_style else line.style
+ tokens.append(Text(" " * spaces[index], style=space_style))
+ self[line_index] = Text("").join(tokens)
diff --git a/src/pip/_vendor/rich/control.py b/src/pip/_vendor/rich/control.py
new file mode 100644
index 000000000..88fcb9295
--- /dev/null
+++ b/src/pip/_vendor/rich/control.py
@@ -0,0 +1,225 @@
+import sys
+import time
+from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Union
+
+if sys.version_info >= (3, 8):
+ from typing import Final
+else:
+ from pip._vendor.typing_extensions import Final # pragma: no cover
+
+from .segment import ControlCode, ControlType, Segment
+
+if TYPE_CHECKING:
+ from .console import Console, ConsoleOptions, RenderResult
+
+STRIP_CONTROL_CODES: Final = [
+ 7, # Bell
+ 8, # Backspace
+ 11, # Vertical tab
+ 12, # Form feed
+ 13, # Carriage return
+]
+_CONTROL_STRIP_TRANSLATE: Final = {
+ _codepoint: None for _codepoint in STRIP_CONTROL_CODES
+}
+
+CONTROL_ESCAPE: Final = {
+ 7: "\\a",
+ 8: "\\b",
+ 11: "\\v",
+ 12: "\\f",
+ 13: "\\r",
+}
+
+CONTROL_CODES_FORMAT: Dict[int, Callable[..., str]] = {
+ ControlType.BELL: lambda: "\x07",
+ ControlType.CARRIAGE_RETURN: lambda: "\r",
+ ControlType.HOME: lambda: "\x1b[H",
+ ControlType.CLEAR: lambda: "\x1b[2J",
+ ControlType.ENABLE_ALT_SCREEN: lambda: "\x1b[?1049h",
+ ControlType.DISABLE_ALT_SCREEN: lambda: "\x1b[?1049l",
+ ControlType.SHOW_CURSOR: lambda: "\x1b[?25h",
+ ControlType.HIDE_CURSOR: lambda: "\x1b[?25l",
+ ControlType.CURSOR_UP: lambda param: f"\x1b[{param}A",
+ ControlType.CURSOR_DOWN: lambda param: f"\x1b[{param}B",
+ ControlType.CURSOR_FORWARD: lambda param: f"\x1b[{param}C",
+ ControlType.CURSOR_BACKWARD: lambda param: f"\x1b[{param}D",
+ ControlType.CURSOR_MOVE_TO_COLUMN: lambda param: f"\x1b[{param+1}G",
+ ControlType.ERASE_IN_LINE: lambda param: f"\x1b[{param}K",
+ ControlType.CURSOR_MOVE_TO: lambda x, y: f"\x1b[{y+1};{x+1}H",
+ ControlType.SET_WINDOW_TITLE: lambda title: f"\x1b]0;{title}\x07",
+}
+
+
+class Control:
+ """A renderable that inserts a control code (non printable but may move cursor).
+
+ Args:
+ *codes (str): Positional arguments are either a :class:`~rich.segment.ControlType` enum or a
+ tuple of ControlType and an integer parameter
+ """
+
+ __slots__ = ["segment"]
+
+ def __init__(self, *codes: Union[ControlType, ControlCode]) -> None:
+ control_codes: List[ControlCode] = [
+ (code,) if isinstance(code, ControlType) else code for code in codes
+ ]
+ _format_map = CONTROL_CODES_FORMAT
+ rendered_codes = "".join(
+ _format_map[code](*parameters) for code, *parameters in control_codes
+ )
+ self.segment = Segment(rendered_codes, None, control_codes)
+
+ @classmethod
+ def bell(cls) -> "Control":
+ """Ring the 'bell'."""
+ return cls(ControlType.BELL)
+
+ @classmethod
+ def home(cls) -> "Control":
+ """Move cursor to 'home' position."""
+ return cls(ControlType.HOME)
+
+ @classmethod
+ def move(cls, x: int = 0, y: int = 0) -> "Control":
+ """Move cursor relative to current position.
+
+ Args:
+ x (int): X offset.
+ y (int): Y offset.
+
+ Returns:
+ ~Control: Control object.
+
+ """
+
+ def get_codes() -> Iterable[ControlCode]:
+ control = ControlType
+ if x:
+ yield (
+ control.CURSOR_FORWARD if x > 0 else control.CURSOR_BACKWARD,
+ abs(x),
+ )
+ if y:
+ yield (
+ control.CURSOR_DOWN if y > 0 else control.CURSOR_UP,
+ abs(y),
+ )
+
+ control = cls(*get_codes())
+ return control
+
+ @classmethod
+ def move_to_column(cls, x: int, y: int = 0) -> "Control":
+ """Move to the given column, optionally add offset to row.
+
+ Returns:
+ x (int): absolute x (column)
+ y (int): optional y offset (row)
+
+ Returns:
+ ~Control: Control object.
+ """
+
+ return (
+ cls(
+ (ControlType.CURSOR_MOVE_TO_COLUMN, x),
+ (
+ ControlType.CURSOR_DOWN if y > 0 else ControlType.CURSOR_UP,
+ abs(y),
+ ),
+ )
+ if y
+ else cls((ControlType.CURSOR_MOVE_TO_COLUMN, x))
+ )
+
+ @classmethod
+ def move_to(cls, x: int, y: int) -> "Control":
+ """Move cursor to absolute position.
+
+ Args:
+ x (int): x offset (column)
+ y (int): y offset (row)
+
+ Returns:
+ ~Control: Control object.
+ """
+ return cls((ControlType.CURSOR_MOVE_TO, x, y))
+
+ @classmethod
+ def clear(cls) -> "Control":
+ """Clear the screen."""
+ return cls(ControlType.CLEAR)
+
+ @classmethod
+ def show_cursor(cls, show: bool) -> "Control":
+ """Show or hide the cursor."""
+ return cls(ControlType.SHOW_CURSOR if show else ControlType.HIDE_CURSOR)
+
+ @classmethod
+ def alt_screen(cls, enable: bool) -> "Control":
+ """Enable or disable alt screen."""
+ if enable:
+ return cls(ControlType.ENABLE_ALT_SCREEN, ControlType.HOME)
+ else:
+ return cls(ControlType.DISABLE_ALT_SCREEN)
+
+ @classmethod
+ def title(cls, title: str) -> "Control":
+ """Set the terminal window title
+
+ Args:
+ title (str): The new terminal window title
+ """
+ return cls((ControlType.SET_WINDOW_TITLE, title))
+
+ def __str__(self) -> str:
+ return self.segment.text
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ if self.segment.text:
+ yield self.segment
+
+
+def strip_control_codes(
+ text: str, _translate_table: Dict[int, None] = _CONTROL_STRIP_TRANSLATE
+) -> str:
+ """Remove control codes from text.
+
+ Args:
+ text (str): A string possibly contain control codes.
+
+ Returns:
+ str: String with control codes removed.
+ """
+ return text.translate(_translate_table)
+
+
+def escape_control_codes(
+ text: str,
+ _translate_table: Dict[int, str] = CONTROL_ESCAPE,
+) -> str:
+ """Replace control codes with their "escaped" equivalent in the given text.
+ (e.g. "\b" becomes "\\b")
+
+ Args:
+ text (str): A string possibly containing control codes.
+
+ Returns:
+ str: String with control codes replaced with their escaped version.
+ """
+ return text.translate(_translate_table)
+
+
+if __name__ == "__main__": # pragma: no cover
+ from pip._vendor.rich.console import Console
+
+ console = Console()
+ console.print("Look at the title of your terminal window ^")
+ # console.print(Control((ControlType.SET_WINDOW_TITLE, "Hello, world!")))
+ for i in range(10):
+ console.set_window_title("🚀 Loading" + "." * i)
+ time.sleep(0.5)
diff --git a/src/pip/_vendor/rich/default_styles.py b/src/pip/_vendor/rich/default_styles.py
new file mode 100644
index 000000000..46e9ea52c
--- /dev/null
+++ b/src/pip/_vendor/rich/default_styles.py
@@ -0,0 +1,188 @@
+from typing import Dict
+
+from .style import Style
+
+DEFAULT_STYLES: Dict[str, Style] = {
+ "none": Style.null(),
+ "reset": Style(
+ color="default",
+ bgcolor="default",
+ dim=False,
+ bold=False,
+ italic=False,
+ underline=False,
+ blink=False,
+ blink2=False,
+ reverse=False,
+ conceal=False,
+ strike=False,
+ ),
+ "dim": Style(dim=True),
+ "bright": Style(dim=False),
+ "bold": Style(bold=True),
+ "strong": Style(bold=True),
+ "code": Style(reverse=True, bold=True),
+ "italic": Style(italic=True),
+ "emphasize": Style(italic=True),
+ "underline": Style(underline=True),
+ "blink": Style(blink=True),
+ "blink2": Style(blink2=True),
+ "reverse": Style(reverse=True),
+ "strike": Style(strike=True),
+ "black": Style(color="black"),
+ "red": Style(color="red"),
+ "green": Style(color="green"),
+ "yellow": Style(color="yellow"),
+ "magenta": Style(color="magenta"),
+ "cyan": Style(color="cyan"),
+ "white": Style(color="white"),
+ "inspect.attr": Style(color="yellow", italic=True),
+ "inspect.attr.dunder": Style(color="yellow", italic=True, dim=True),
+ "inspect.callable": Style(bold=True, color="red"),
+ "inspect.async_def": Style(italic=True, color="bright_cyan"),
+ "inspect.def": Style(italic=True, color="bright_cyan"),
+ "inspect.class": Style(italic=True, color="bright_cyan"),
+ "inspect.error": Style(bold=True, color="red"),
+ "inspect.equals": Style(),
+ "inspect.help": Style(color="cyan"),
+ "inspect.doc": Style(dim=True),
+ "inspect.value.border": Style(color="green"),
+ "live.ellipsis": Style(bold=True, color="red"),
+ "layout.tree.row": Style(dim=False, color="red"),
+ "layout.tree.column": Style(dim=False, color="blue"),
+ "logging.keyword": Style(bold=True, color="yellow"),
+ "logging.level.notset": Style(dim=True),
+ "logging.level.debug": Style(color="green"),
+ "logging.level.info": Style(color="blue"),
+ "logging.level.warning": Style(color="red"),
+ "logging.level.error": Style(color="red", bold=True),
+ "logging.level.critical": Style(color="red", bold=True, reverse=True),
+ "log.level": Style.null(),
+ "log.time": Style(color="cyan", dim=True),
+ "log.message": Style.null(),
+ "log.path": Style(dim=True),
+ "repr.ellipsis": Style(color="yellow"),
+ "repr.indent": Style(color="green", dim=True),
+ "repr.error": Style(color="red", bold=True),
+ "repr.str": Style(color="green", italic=False, bold=False),
+ "repr.brace": Style(bold=True),
+ "repr.comma": Style(bold=True),
+ "repr.ipv4": Style(bold=True, color="bright_green"),
+ "repr.ipv6": Style(bold=True, color="bright_green"),
+ "repr.eui48": Style(bold=True, color="bright_green"),
+ "repr.eui64": Style(bold=True, color="bright_green"),
+ "repr.tag_start": Style(bold=True),
+ "repr.tag_name": Style(color="bright_magenta", bold=True),
+ "repr.tag_contents": Style(color="default"),
+ "repr.tag_end": Style(bold=True),
+ "repr.attrib_name": Style(color="yellow", italic=False),
+ "repr.attrib_equal": Style(bold=True),
+ "repr.attrib_value": Style(color="magenta", italic=False),
+ "repr.number": Style(color="cyan", bold=True, italic=False),
+ "repr.number_complex": Style(color="cyan", bold=True, italic=False), # same
+ "repr.bool_true": Style(color="bright_green", italic=True),
+ "repr.bool_false": Style(color="bright_red", italic=True),
+ "repr.none": Style(color="magenta", italic=True),
+ "repr.url": Style(underline=True, color="bright_blue", italic=False, bold=False),
+ "repr.uuid": Style(color="bright_yellow", bold=False),
+ "repr.call": Style(color="magenta", bold=True),
+ "repr.path": Style(color="magenta"),
+ "repr.filename": Style(color="bright_magenta"),
+ "rule.line": Style(color="bright_green"),
+ "rule.text": Style.null(),
+ "json.brace": Style(bold=True),
+ "json.bool_true": Style(color="bright_green", italic=True),
+ "json.bool_false": Style(color="bright_red", italic=True),
+ "json.null": Style(color="magenta", italic=True),
+ "json.number": Style(color="cyan", bold=True, italic=False),
+ "json.str": Style(color="green", italic=False, bold=False),
+ "json.key": Style(color="blue", bold=True),
+ "prompt": Style.null(),
+ "prompt.choices": Style(color="magenta", bold=True),
+ "prompt.default": Style(color="cyan", bold=True),
+ "prompt.invalid": Style(color="red"),
+ "prompt.invalid.choice": Style(color="red"),
+ "pretty": Style.null(),
+ "scope.border": Style(color="blue"),
+ "scope.key": Style(color="yellow", italic=True),
+ "scope.key.special": Style(color="yellow", italic=True, dim=True),
+ "scope.equals": Style(color="red"),
+ "table.header": Style(bold=True),
+ "table.footer": Style(bold=True),
+ "table.cell": Style.null(),
+ "table.title": Style(italic=True),
+ "table.caption": Style(italic=True, dim=True),
+ "traceback.error": Style(color="red", italic=True),
+ "traceback.border.syntax_error": Style(color="bright_red"),
+ "traceback.border": Style(color="red"),
+ "traceback.text": Style.null(),
+ "traceback.title": Style(color="red", bold=True),
+ "traceback.exc_type": Style(color="bright_red", bold=True),
+ "traceback.exc_value": Style.null(),
+ "traceback.offset": Style(color="bright_red", bold=True),
+ "bar.back": Style(color="grey23"),
+ "bar.complete": Style(color="rgb(249,38,114)"),
+ "bar.finished": Style(color="rgb(114,156,31)"),
+ "bar.pulse": Style(color="rgb(249,38,114)"),
+ "progress.description": Style.null(),
+ "progress.filesize": Style(color="green"),
+ "progress.filesize.total": Style(color="green"),
+ "progress.download": Style(color="green"),
+ "progress.elapsed": Style(color="yellow"),
+ "progress.percentage": Style(color="magenta"),
+ "progress.remaining": Style(color="cyan"),
+ "progress.data.speed": Style(color="red"),
+ "progress.spinner": Style(color="green"),
+ "status.spinner": Style(color="green"),
+ "tree": Style(),
+ "tree.line": Style(),
+ "markdown.paragraph": Style(),
+ "markdown.text": Style(),
+ "markdown.emph": Style(italic=True),
+ "markdown.strong": Style(bold=True),
+ "markdown.code": Style(bgcolor="black", color="bright_white"),
+ "markdown.code_block": Style(dim=True, color="cyan", bgcolor="black"),
+ "markdown.block_quote": Style(color="magenta"),
+ "markdown.list": Style(color="cyan"),
+ "markdown.item": Style(),
+ "markdown.item.bullet": Style(color="yellow", bold=True),
+ "markdown.item.number": Style(color="yellow", bold=True),
+ "markdown.hr": Style(color="yellow"),
+ "markdown.h1.border": Style(),
+ "markdown.h1": Style(bold=True),
+ "markdown.h2": Style(bold=True, underline=True),
+ "markdown.h3": Style(bold=True),
+ "markdown.h4": Style(bold=True, dim=True),
+ "markdown.h5": Style(underline=True),
+ "markdown.h6": Style(italic=True),
+ "markdown.h7": Style(italic=True, dim=True),
+ "markdown.link": Style(color="bright_blue"),
+ "markdown.link_url": Style(color="blue"),
+ "iso8601.date": Style(color="blue"),
+ "iso8601.time": Style(color="magenta"),
+ "iso8601.timezone": Style(color="yellow"),
+}
+
+
+if __name__ == "__main__": # pragma: no cover
+ import argparse
+ import io
+
+ from pip._vendor.rich.console import Console
+ from pip._vendor.rich.table import Table
+ from pip._vendor.rich.text import Text
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--html", action="store_true", help="Export as HTML table")
+ args = parser.parse_args()
+ html: bool = args.html
+ console = Console(record=True, width=70, file=io.StringIO()) if html else Console()
+
+ table = Table("Name", "Styling")
+
+ for style_name, style in DEFAULT_STYLES.items():
+ table.add_row(Text(style_name, style=style), str(style))
+
+ console.print(table)
+ if html:
+ print(console.export_html(inline_styles=True))
diff --git a/src/pip/_vendor/rich/diagnose.py b/src/pip/_vendor/rich/diagnose.py
new file mode 100644
index 000000000..ad3618389
--- /dev/null
+++ b/src/pip/_vendor/rich/diagnose.py
@@ -0,0 +1,37 @@
+import os
+import platform
+
+from pip._vendor.rich import inspect
+from pip._vendor.rich.console import Console, get_windows_console_features
+from pip._vendor.rich.panel import Panel
+from pip._vendor.rich.pretty import Pretty
+
+
+def report() -> None: # pragma: no cover
+ """Print a report to the terminal with debugging information"""
+ console = Console()
+ inspect(console)
+ features = get_windows_console_features()
+ inspect(features)
+
+ env_names = (
+ "TERM",
+ "COLORTERM",
+ "CLICOLOR",
+ "NO_COLOR",
+ "TERM_PROGRAM",
+ "COLUMNS",
+ "LINES",
+ "JUPYTER_COLUMNS",
+ "JUPYTER_LINES",
+ "JPY_PARENT_PID",
+ "VSCODE_VERBOSE_LOGGING",
+ )
+ env = {name: os.getenv(name) for name in env_names}
+ console.print(Panel.fit((Pretty(env)), title="[b]Environment Variables"))
+
+ console.print(f'platform="{platform.system()}"')
+
+
+if __name__ == "__main__": # pragma: no cover
+ report()
diff --git a/src/pip/_vendor/rich/emoji.py b/src/pip/_vendor/rich/emoji.py
new file mode 100644
index 000000000..791f0465d
--- /dev/null
+++ b/src/pip/_vendor/rich/emoji.py
@@ -0,0 +1,96 @@
+import sys
+from typing import TYPE_CHECKING, Optional, Union
+
+from .jupyter import JupyterMixin
+from .segment import Segment
+from .style import Style
+from ._emoji_codes import EMOJI
+from ._emoji_replace import _emoji_replace
+
+if sys.version_info >= (3, 8):
+ from typing import Literal
+else:
+ from pip._vendor.typing_extensions import Literal # pragma: no cover
+
+
+if TYPE_CHECKING:
+ from .console import Console, ConsoleOptions, RenderResult
+
+
+EmojiVariant = Literal["emoji", "text"]
+
+
+class NoEmoji(Exception):
+ """No emoji by that name."""
+
+
+class Emoji(JupyterMixin):
+ __slots__ = ["name", "style", "_char", "variant"]
+
+ VARIANTS = {"text": "\uFE0E", "emoji": "\uFE0F"}
+
+ def __init__(
+ self,
+ name: str,
+ style: Union[str, Style] = "none",
+ variant: Optional[EmojiVariant] = None,
+ ) -> None:
+ """A single emoji character.
+
+ Args:
+ name (str): Name of emoji.
+ style (Union[str, Style], optional): Optional style. Defaults to None.
+
+ Raises:
+ NoEmoji: If the emoji doesn't exist.
+ """
+ self.name = name
+ self.style = style
+ self.variant = variant
+ try:
+ self._char = EMOJI[name]
+ except KeyError:
+ raise NoEmoji(f"No emoji called {name!r}")
+ if variant is not None:
+ self._char += self.VARIANTS.get(variant, "")
+
+ @classmethod
+ def replace(cls, text: str) -> str:
+ """Replace emoji markup with corresponding unicode characters.
+
+ Args:
+ text (str): A string with emojis codes, e.g. "Hello :smiley:!"
+
+ Returns:
+ str: A string with emoji codes replaces with actual emoji.
+ """
+ return _emoji_replace(text)
+
+ def __repr__(self) -> str:
+ return f"<emoji {self.name!r}>"
+
+ def __str__(self) -> str:
+ return self._char
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ yield Segment(self._char, console.get_style(self.style))
+
+
+if __name__ == "__main__": # pragma: no cover
+ import sys
+
+ from pip._vendor.rich.columns import Columns
+ from pip._vendor.rich.console import Console
+
+ console = Console(record=True)
+
+ columns = Columns(
+ (f":{name}: {name}" for name in sorted(EMOJI.keys()) if "\u200D" not in name),
+ column_first=True,
+ )
+
+ console.print(columns)
+ if len(sys.argv) > 1:
+ console.save_html(sys.argv[1])
diff --git a/src/pip/_vendor/rich/errors.py b/src/pip/_vendor/rich/errors.py
new file mode 100644
index 000000000..0bcbe53ef
--- /dev/null
+++ b/src/pip/_vendor/rich/errors.py
@@ -0,0 +1,34 @@
+class ConsoleError(Exception):
+ """An error in console operation."""
+
+
+class StyleError(Exception):
+ """An error in styles."""
+
+
+class StyleSyntaxError(ConsoleError):
+ """Style was badly formatted."""
+
+
+class MissingStyle(StyleError):
+ """No such style."""
+
+
+class StyleStackError(ConsoleError):
+ """Style stack is invalid."""
+
+
+class NotRenderableError(ConsoleError):
+ """Object is not renderable."""
+
+
+class MarkupError(ConsoleError):
+ """Markup was badly formatted."""
+
+
+class LiveError(ConsoleError):
+ """Error related to Live display."""
+
+
+class NoAltScreen(ConsoleError):
+ """Alt screen mode was required."""
diff --git a/src/pip/_vendor/rich/file_proxy.py b/src/pip/_vendor/rich/file_proxy.py
new file mode 100644
index 000000000..cc69f22f3
--- /dev/null
+++ b/src/pip/_vendor/rich/file_proxy.py
@@ -0,0 +1,54 @@
+import io
+from typing import IO, TYPE_CHECKING, Any, List
+
+from .ansi import AnsiDecoder
+from .text import Text
+
+if TYPE_CHECKING:
+ from .console import Console
+
+
+class FileProxy(io.TextIOBase):
+ """Wraps a file (e.g. sys.stdout) and redirects writes to a console."""
+
+ def __init__(self, console: "Console", file: IO[str]) -> None:
+ self.__console = console
+ self.__file = file
+ self.__buffer: List[str] = []
+ self.__ansi_decoder = AnsiDecoder()
+
+ @property
+ def rich_proxied_file(self) -> IO[str]:
+ """Get proxied file."""
+ return self.__file
+
+ def __getattr__(self, name: str) -> Any:
+ return getattr(self.__file, name)
+
+ def write(self, text: str) -> int:
+ if not isinstance(text, str):
+ raise TypeError(f"write() argument must be str, not {type(text).__name__}")
+ buffer = self.__buffer
+ lines: List[str] = []
+ while text:
+ line, new_line, text = text.partition("\n")
+ if new_line:
+ lines.append("".join(buffer) + line)
+ del buffer[:]
+ else:
+ buffer.append(line)
+ break
+ if lines:
+ console = self.__console
+ with console:
+ output = Text("\n").join(
+ self.__ansi_decoder.decode_line(line) for line in lines
+ )
+ console.print(output)
+ return len(text)
+
+ def flush(self) -> None:
+ output = "".join(self.__buffer)
+ if output:
+ self.__console.print(output)
+ del self.__buffer[:]
diff --git a/src/pip/_vendor/rich/filesize.py b/src/pip/_vendor/rich/filesize.py
new file mode 100644
index 000000000..99f118e20
--- /dev/null
+++ b/src/pip/_vendor/rich/filesize.py
@@ -0,0 +1,89 @@
+# coding: utf-8
+"""Functions for reporting filesizes. Borrowed from https://github.com/PyFilesystem/pyfilesystem2
+
+The functions declared in this module should cover the different
+use cases needed to generate a string representation of a file size
+using several different units. Since there are many standards regarding
+file size units, three different functions have been implemented.
+
+See Also:
+ * `Wikipedia: Binary prefix <https://en.wikipedia.org/wiki/Binary_prefix>`_
+
+"""
+
+__all__ = ["decimal"]
+
+from typing import Iterable, List, Optional, Tuple
+
+
+def _to_str(
+ size: int,
+ suffixes: Iterable[str],
+ base: int,
+ *,
+ precision: Optional[int] = 1,
+ separator: Optional[str] = " ",
+) -> str:
+ if size == 1:
+ return "1 byte"
+ elif size < base:
+ return "{:,} bytes".format(size)
+
+ for i, suffix in enumerate(suffixes, 2): # noqa: B007
+ unit = base**i
+ if size < unit:
+ break
+ return "{:,.{precision}f}{separator}{}".format(
+ (base * size / unit),
+ suffix,
+ precision=precision,
+ separator=separator,
+ )
+
+
+def pick_unit_and_suffix(size: int, suffixes: List[str], base: int) -> Tuple[int, str]:
+ """Pick a suffix and base for the given size."""
+ for i, suffix in enumerate(suffixes):
+ unit = base**i
+ if size < unit * base:
+ break
+ return unit, suffix
+
+
+def decimal(
+ size: int,
+ *,
+ precision: Optional[int] = 1,
+ separator: Optional[str] = " ",
+) -> str:
+ """Convert a filesize in to a string (powers of 1000, SI prefixes).
+
+ In this convention, ``1000 B = 1 kB``.
+
+ This is typically the format used to advertise the storage
+ capacity of USB flash drives and the like (*256 MB* meaning
+ actually a storage capacity of more than *256 000 000 B*),
+ or used by **Mac OS X** since v10.6 to report file sizes.
+
+ Arguments:
+ int (size): A file size.
+ int (precision): The number of decimal places to include (default = 1).
+ str (separator): The string to separate the value from the units (default = " ").
+
+ Returns:
+ `str`: A string containing a abbreviated file size and units.
+
+ Example:
+ >>> filesize.decimal(30000)
+ '30.0 kB'
+ >>> filesize.decimal(30000, precision=2, separator="")
+ '30.00kB'
+
+ """
+ return _to_str(
+ size,
+ ("kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"),
+ 1000,
+ precision=precision,
+ separator=separator,
+ )
diff --git a/src/pip/_vendor/rich/highlighter.py b/src/pip/_vendor/rich/highlighter.py
new file mode 100644
index 000000000..82293dffc
--- /dev/null
+++ b/src/pip/_vendor/rich/highlighter.py
@@ -0,0 +1,232 @@
+import re
+from abc import ABC, abstractmethod
+from typing import List, Union
+
+from .text import Span, Text
+
+
+def _combine_regex(*regexes: str) -> str:
+ """Combine a number of regexes in to a single regex.
+
+ Returns:
+ str: New regex with all regexes ORed together.
+ """
+ return "|".join(regexes)
+
+
+class Highlighter(ABC):
+ """Abstract base class for highlighters."""
+
+ def __call__(self, text: Union[str, Text]) -> Text:
+ """Highlight a str or Text instance.
+
+ Args:
+ text (Union[str, ~Text]): Text to highlight.
+
+ Raises:
+ TypeError: If not called with text or str.
+
+ Returns:
+ Text: A test instance with highlighting applied.
+ """
+ if isinstance(text, str):
+ highlight_text = Text(text)
+ elif isinstance(text, Text):
+ highlight_text = text.copy()
+ else:
+ raise TypeError(f"str or Text instance required, not {text!r}")
+ self.highlight(highlight_text)
+ return highlight_text
+
+ @abstractmethod
+ def highlight(self, text: Text) -> None:
+ """Apply highlighting in place to text.
+
+ Args:
+ text (~Text): A text object highlight.
+ """
+
+
+class NullHighlighter(Highlighter):
+ """A highlighter object that doesn't highlight.
+
+ May be used to disable highlighting entirely.
+
+ """
+
+ def highlight(self, text: Text) -> None:
+ """Nothing to do"""
+
+
+class RegexHighlighter(Highlighter):
+ """Applies highlighting from a list of regular expressions."""
+
+ highlights: List[str] = []
+ base_style: str = ""
+
+ def highlight(self, text: Text) -> None:
+ """Highlight :class:`rich.text.Text` using regular expressions.
+
+ Args:
+ text (~Text): Text to highlighted.
+
+ """
+
+ highlight_regex = text.highlight_regex
+ for re_highlight in self.highlights:
+ highlight_regex(re_highlight, style_prefix=self.base_style)
+
+
+class ReprHighlighter(RegexHighlighter):
+ """Highlights the text typically produced from ``__repr__`` methods."""
+
+ base_style = "repr."
+ highlights = [
+ r"(?P<tag_start><)(?P<tag_name>[-\w.:|]*)(?P<tag_contents>[\w\W]*?)(?P<tag_end>>)",
+ r'(?P<attrib_name>[\w_]{1,50})=(?P<attrib_value>"?[\w_]+"?)?',
+ r"(?P<brace>[][{}()])",
+ _combine_regex(
+ r"(?P<ipv4>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})",
+ r"(?P<ipv6>([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})",
+ r"(?P<eui64>(?:[0-9A-Fa-f]{1,2}-){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){3}[0-9A-Fa-f]{4})",
+ r"(?P<eui48>(?:[0-9A-Fa-f]{1,2}-){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4})",
+ r"(?P<uuid>[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})",
+ r"(?P<call>[\w.]*?)\(",
+ r"\b(?P<bool_true>True)\b|\b(?P<bool_false>False)\b|\b(?P<none>None)\b",
+ r"(?P<ellipsis>\.\.\.)",
+ r"(?P<number_complex>(?<!\w)(?:\-?[0-9]+\.?[0-9]*(?:e[-+]?\d+?)?)(?:[-+](?:[0-9]+\.?[0-9]*(?:e[-+]?\d+)?))?j)",
+ r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[-+]?\d+?)?\b|0x[0-9a-fA-F]*)",
+ r"(?P<path>\B(/[-\w._+]+)*\/)(?P<filename>[-\w._+]*)?",
+ r"(?<![\\\w])(?P<str>b?'''.*?(?<!\\)'''|b?'.*?(?<!\\)'|b?\"\"\".*?(?<!\\)\"\"\"|b?\".*?(?<!\\)\")",
+ r"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#]*)",
+ ),
+ ]
+
+
+class JSONHighlighter(RegexHighlighter):
+ """Highlights JSON"""
+
+ # Captures the start and end of JSON strings, handling escaped quotes
+ JSON_STR = r"(?<![\\\w])(?P<str>b?\".*?(?<!\\)\")"
+ JSON_WHITESPACE = {" ", "\n", "\r", "\t"}
+
+ base_style = "json."
+ highlights = [
+ _combine_regex(
+ r"(?P<brace>[\{\[\(\)\]\}])",
+ r"\b(?P<bool_true>true)\b|\b(?P<bool_false>false)\b|\b(?P<null>null)\b",
+ r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[\-\+]?\d+?)?\b|0x[0-9a-fA-F]*)",
+ JSON_STR,
+ ),
+ ]
+
+ def highlight(self, text: Text) -> None:
+ super().highlight(text)
+
+ # Additional work to handle highlighting JSON keys
+ plain = text.plain
+ append = text.spans.append
+ whitespace = self.JSON_WHITESPACE
+ for match in re.finditer(self.JSON_STR, plain):
+ start, end = match.span()
+ cursor = end
+ while cursor < len(plain):
+ char = plain[cursor]
+ cursor += 1
+ if char == ":":
+ append(Span(start, end, "json.key"))
+ elif char in whitespace:
+ continue
+ break
+
+
+class ISO8601Highlighter(RegexHighlighter):
+ """Highlights the ISO8601 date time strings.
+ Regex reference: https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch04s07.html
+ """
+
+ base_style = "iso8601."
+ highlights = [
+ #
+ # Dates
+ #
+ # Calendar month (e.g. 2008-08). The hyphen is required
+ r"^(?P<year>[0-9]{4})-(?P<month>1[0-2]|0[1-9])$",
+ # Calendar date w/o hyphens (e.g. 20080830)
+ r"^(?P<date>(?P<year>[0-9]{4})(?P<month>1[0-2]|0[1-9])(?P<day>3[01]|0[1-9]|[12][0-9]))$",
+ # Ordinal date (e.g. 2008-243). The hyphen is optional
+ r"^(?P<date>(?P<year>[0-9]{4})-?(?P<day>36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9]))$",
+ #
+ # Weeks
+ #
+ # Week of the year (e.g., 2008-W35). The hyphen is optional
+ r"^(?P<date>(?P<year>[0-9]{4})-?W(?P<week>5[0-3]|[1-4][0-9]|0[1-9]))$",
+ # Week date (e.g., 2008-W35-6). The hyphens are optional
+ r"^(?P<date>(?P<year>[0-9]{4})-?W(?P<week>5[0-3]|[1-4][0-9]|0[1-9])-?(?P<day>[1-7]))$",
+ #
+ # Times
+ #
+ # Hours and minutes (e.g., 17:21). The colon is optional
+ r"^(?P<time>(?P<hour>2[0-3]|[01][0-9]):?(?P<minute>[0-5][0-9]))$",
+ # Hours, minutes, and seconds w/o colons (e.g., 172159)
+ r"^(?P<time>(?P<hour>2[0-3]|[01][0-9])(?P<minute>[0-5][0-9])(?P<second>[0-5][0-9]))$",
+ # Time zone designator (e.g., Z, +07 or +07:00). The colons and the minutes are optional
+ r"^(?P<timezone>(Z|[+-](?:2[0-3]|[01][0-9])(?::?(?:[0-5][0-9]))?))$",
+ # Hours, minutes, and seconds with time zone designator (e.g., 17:21:59+07:00).
+ # All the colons are optional. The minutes in the time zone designator are also optional
+ r"^(?P<time>(?P<hour>2[0-3]|[01][0-9])(?P<minute>[0-5][0-9])(?P<second>[0-5][0-9]))(?P<timezone>Z|[+-](?:2[0-3]|[01][0-9])(?::?(?:[0-5][0-9]))?)$",
+ #
+ # Date and Time
+ #
+ # Calendar date with hours, minutes, and seconds (e.g., 2008-08-30 17:21:59 or 20080830 172159).
+ # A space is required between the date and the time. The hyphens and colons are optional.
+ # This regex matches dates and times that specify some hyphens or colons but omit others.
+ # This does not follow ISO 8601
+ r"^(?P<date>(?P<year>[0-9]{4})(?P<hyphen>-)?(?P<month>1[0-2]|0[1-9])(?(hyphen)-)(?P<day>3[01]|0[1-9]|[12][0-9])) (?P<time>(?P<hour>2[0-3]|[01][0-9])(?(hyphen):)(?P<minute>[0-5][0-9])(?(hyphen):)(?P<second>[0-5][0-9]))$",
+ #
+ # XML Schema dates and times
+ #
+ # Date, with optional time zone (e.g., 2008-08-30 or 2008-08-30+07:00).
+ # Hyphens are required. This is the XML Schema 'date' type
+ r"^(?P<date>(?P<year>-?(?:[1-9][0-9]*)?[0-9]{4})-(?P<month>1[0-2]|0[1-9])-(?P<day>3[01]|0[1-9]|[12][0-9]))(?P<timezone>Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$",
+ # Time, with optional fractional seconds and time zone (e.g., 01:45:36 or 01:45:36.123+07:00).
+ # There is no limit on the number of digits for the fractional seconds. This is the XML Schema 'time' type
+ r"^(?P<time>(?P<hour>2[0-3]|[01][0-9]):(?P<minute>[0-5][0-9]):(?P<second>[0-5][0-9])(?P<frac>\.[0-9]+)?)(?P<timezone>Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$",
+ # Date and time, with optional fractional seconds and time zone (e.g., 2008-08-30T01:45:36 or 2008-08-30T01:45:36.123Z).
+ # This is the XML Schema 'dateTime' type
+ r"^(?P<date>(?P<year>-?(?:[1-9][0-9]*)?[0-9]{4})-(?P<month>1[0-2]|0[1-9])-(?P<day>3[01]|0[1-9]|[12][0-9]))T(?P<time>(?P<hour>2[0-3]|[01][0-9]):(?P<minute>[0-5][0-9]):(?P<second>[0-5][0-9])(?P<ms>\.[0-9]+)?)(?P<timezone>Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$",
+ ]
+
+
+if __name__ == "__main__": # pragma: no cover
+ from .console import Console
+
+ console = Console()
+ console.print("[bold green]hello world![/bold green]")
+ console.print("'[bold green]hello world![/bold green]'")
+
+ console.print(" /foo")
+ console.print("/foo/")
+ console.print("/foo/bar")
+ console.print("foo/bar/baz")
+
+ console.print("/foo/bar/baz?foo=bar+egg&egg=baz")
+ console.print("/foo/bar/baz/")
+ console.print("/foo/bar/baz/egg")
+ console.print("/foo/bar/baz/egg.py")
+ console.print("/foo/bar/baz/egg.py word")
+ console.print(" /foo/bar/baz/egg.py word")
+ console.print("foo /foo/bar/baz/egg.py word")
+ console.print("foo /foo/bar/ba._++z/egg+.py word")
+ console.print("https://example.org?foo=bar#header")
+
+ console.print(1234567.34)
+ console.print(1 / 2)
+ console.print(-1 / 123123123123)
+
+ console.print(
+ "127.0.1.1 bar 192.168.1.4 2001:0db8:85a3:0000:0000:8a2e:0370:7334 foo"
+ )
+ import json
+
+ console.print_json(json.dumps(obj={"name": "apple", "count": 1}), indent=None)
diff --git a/src/pip/_vendor/rich/json.py b/src/pip/_vendor/rich/json.py
new file mode 100644
index 000000000..21b642ab8
--- /dev/null
+++ b/src/pip/_vendor/rich/json.py
@@ -0,0 +1,140 @@
+from json import loads, dumps
+from typing import Any, Callable, Optional, Union
+
+from .text import Text
+from .highlighter import JSONHighlighter, NullHighlighter
+
+
+class JSON:
+ """A renderable which pretty prints JSON.
+
+ Args:
+ json (str): JSON encoded data.
+ indent (Union[None, int, str], optional): Number of characters to indent by. Defaults to 2.
+ highlight (bool, optional): Enable highlighting. Defaults to True.
+ skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False.
+ ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False.
+ check_circular (bool, optional): Check for circular references. Defaults to True.
+ allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True.
+ default (Callable, optional): A callable that converts values that can not be encoded
+ in to something that can be JSON encoded. Defaults to None.
+ sort_keys (bool, optional): Sort dictionary keys. Defaults to False.
+ """
+
+ def __init__(
+ self,
+ json: str,
+ indent: Union[None, int, str] = 2,
+ highlight: bool = True,
+ skip_keys: bool = False,
+ ensure_ascii: bool = False,
+ check_circular: bool = True,
+ allow_nan: bool = True,
+ default: Optional[Callable[[Any], Any]] = None,
+ sort_keys: bool = False,
+ ) -> None:
+ data = loads(json)
+ json = dumps(
+ data,
+ indent=indent,
+ skipkeys=skip_keys,
+ ensure_ascii=ensure_ascii,
+ check_circular=check_circular,
+ allow_nan=allow_nan,
+ default=default,
+ sort_keys=sort_keys,
+ )
+ highlighter = JSONHighlighter() if highlight else NullHighlighter()
+ self.text = highlighter(json)
+ self.text.no_wrap = True
+ self.text.overflow = None
+
+ @classmethod
+ def from_data(
+ cls,
+ data: Any,
+ indent: Union[None, int, str] = 2,
+ highlight: bool = True,
+ skip_keys: bool = False,
+ ensure_ascii: bool = False,
+ check_circular: bool = True,
+ allow_nan: bool = True,
+ default: Optional[Callable[[Any], Any]] = None,
+ sort_keys: bool = False,
+ ) -> "JSON":
+ """Encodes a JSON object from arbitrary data.
+
+ Args:
+ data (Any): An object that may be encoded in to JSON
+ indent (Union[None, int, str], optional): Number of characters to indent by. Defaults to 2.
+ highlight (bool, optional): Enable highlighting. Defaults to True.
+ default (Callable, optional): Optional callable which will be called for objects that cannot be serialized. Defaults to None.
+ skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False.
+ ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False.
+ check_circular (bool, optional): Check for circular references. Defaults to True.
+ allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True.
+ default (Callable, optional): A callable that converts values that can not be encoded
+ in to something that can be JSON encoded. Defaults to None.
+ sort_keys (bool, optional): Sort dictionary keys. Defaults to False.
+
+ Returns:
+ JSON: New JSON object from the given data.
+ """
+ json_instance: "JSON" = cls.__new__(cls)
+ json = dumps(
+ data,
+ indent=indent,
+ skipkeys=skip_keys,
+ ensure_ascii=ensure_ascii,
+ check_circular=check_circular,
+ allow_nan=allow_nan,
+ default=default,
+ sort_keys=sort_keys,
+ )
+ highlighter = JSONHighlighter() if highlight else NullHighlighter()
+ json_instance.text = highlighter(json)
+ json_instance.text.no_wrap = True
+ json_instance.text.overflow = None
+ return json_instance
+
+ def __rich__(self) -> Text:
+ return self.text
+
+
+if __name__ == "__main__":
+
+ import argparse
+ import sys
+
+ parser = argparse.ArgumentParser(description="Pretty print json")
+ parser.add_argument(
+ "path",
+ metavar="PATH",
+ help="path to file, or - for stdin",
+ )
+ parser.add_argument(
+ "-i",
+ "--indent",
+ metavar="SPACES",
+ type=int,
+ help="Number of spaces in an indent",
+ default=2,
+ )
+ args = parser.parse_args()
+
+ from pip._vendor.rich.console import Console
+
+ console = Console()
+ error_console = Console(stderr=True)
+
+ try:
+ if args.path == "-":
+ json_data = sys.stdin.read()
+ else:
+ with open(args.path, "rt") as json_file:
+ json_data = json_file.read()
+ except Exception as error:
+ error_console.print(f"Unable to read {args.path!r}; {error}")
+ sys.exit(-1)
+
+ console.print(JSON(json_data, indent=args.indent), soft_wrap=True)
diff --git a/src/pip/_vendor/rich/jupyter.py b/src/pip/_vendor/rich/jupyter.py
new file mode 100644
index 000000000..22f4d716a
--- /dev/null
+++ b/src/pip/_vendor/rich/jupyter.py
@@ -0,0 +1,101 @@
+from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Sequence
+
+if TYPE_CHECKING:
+ from pip._vendor.rich.console import ConsoleRenderable
+
+from . import get_console
+from .segment import Segment
+from .terminal_theme import DEFAULT_TERMINAL_THEME
+
+if TYPE_CHECKING:
+ from pip._vendor.rich.console import ConsoleRenderable
+
+JUPYTER_HTML_FORMAT = """\
+<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace">{code}</pre>
+"""
+
+
+class JupyterRenderable:
+ """A shim to write html to Jupyter notebook."""
+
+ def __init__(self, html: str, text: str) -> None:
+ self.html = html
+ self.text = text
+
+ def _repr_mimebundle_(
+ self, include: Sequence[str], exclude: Sequence[str], **kwargs: Any
+ ) -> Dict[str, str]:
+ data = {"text/plain": self.text, "text/html": self.html}
+ if include:
+ data = {k: v for (k, v) in data.items() if k in include}
+ if exclude:
+ data = {k: v for (k, v) in data.items() if k not in exclude}
+ return data
+
+
+class JupyterMixin:
+ """Add to an Rich renderable to make it render in Jupyter notebook."""
+
+ __slots__ = ()
+
+ def _repr_mimebundle_(
+ self: "ConsoleRenderable",
+ include: Sequence[str],
+ exclude: Sequence[str],
+ **kwargs: Any,
+ ) -> Dict[str, str]:
+ console = get_console()
+ segments = list(console.render(self, console.options))
+ html = _render_segments(segments)
+ text = console._render_buffer(segments)
+ data = {"text/plain": text, "text/html": html}
+ if include:
+ data = {k: v for (k, v) in data.items() if k in include}
+ if exclude:
+ data = {k: v for (k, v) in data.items() if k not in exclude}
+ return data
+
+
+def _render_segments(segments: Iterable[Segment]) -> str:
+ def escape(text: str) -> str:
+ """Escape html."""
+ return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
+
+ fragments: List[str] = []
+ append_fragment = fragments.append
+ theme = DEFAULT_TERMINAL_THEME
+ for text, style, control in Segment.simplify(segments):
+ if control:
+ continue
+ text = escape(text)
+ if style:
+ rule = style.get_html_style(theme)
+ text = f'<span style="{rule}">{text}</span>' if rule else text
+ if style.link:
+ text = f'<a href="{style.link}" target="_blank">{text}</a>'
+ append_fragment(text)
+
+ code = "".join(fragments)
+ html = JUPYTER_HTML_FORMAT.format(code=code)
+
+ return html
+
+
+def display(segments: Iterable[Segment], text: str) -> None:
+ """Render segments to Jupyter."""
+ html = _render_segments(segments)
+ jupyter_renderable = JupyterRenderable(html, text)
+ try:
+ from IPython.display import display as ipython_display
+
+ ipython_display(jupyter_renderable)
+ except ModuleNotFoundError:
+ # Handle the case where the Console has force_jupyter=True,
+ # but IPython is not installed.
+ pass
+
+
+def print(*args: Any, **kwargs: Any) -> None:
+ """Proxy for Console print."""
+ console = get_console()
+ return console.print(*args, **kwargs)
diff --git a/src/pip/_vendor/rich/layout.py b/src/pip/_vendor/rich/layout.py
new file mode 100644
index 000000000..849356ea9
--- /dev/null
+++ b/src/pip/_vendor/rich/layout.py
@@ -0,0 +1,443 @@
+from abc import ABC, abstractmethod
+from itertools import islice
+from operator import itemgetter
+from threading import RLock
+from typing import (
+ TYPE_CHECKING,
+ Dict,
+ Iterable,
+ List,
+ NamedTuple,
+ Optional,
+ Sequence,
+ Tuple,
+ Union,
+)
+
+from ._ratio import ratio_resolve
+from .align import Align
+from .console import Console, ConsoleOptions, RenderableType, RenderResult
+from .highlighter import ReprHighlighter
+from .panel import Panel
+from .pretty import Pretty
+from .region import Region
+from .repr import Result, rich_repr
+from .segment import Segment
+from .style import StyleType
+
+if TYPE_CHECKING:
+ from pip._vendor.rich.tree import Tree
+
+
+class LayoutRender(NamedTuple):
+ """An individual layout render."""
+
+ region: Region
+ render: List[List[Segment]]
+
+
+RegionMap = Dict["Layout", Region]
+RenderMap = Dict["Layout", LayoutRender]
+
+
+class LayoutError(Exception):
+ """Layout related error."""
+
+
+class NoSplitter(LayoutError):
+ """Requested splitter does not exist."""
+
+
+class _Placeholder:
+ """An internal renderable used as a Layout placeholder."""
+
+ highlighter = ReprHighlighter()
+
+ def __init__(self, layout: "Layout", style: StyleType = "") -> None:
+ self.layout = layout
+ self.style = style
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ width = options.max_width
+ height = options.height or options.size.height
+ layout = self.layout
+ title = (
+ f"{layout.name!r} ({width} x {height})"
+ if layout.name
+ else f"({width} x {height})"
+ )
+ yield Panel(
+ Align.center(Pretty(layout), vertical="middle"),
+ style=self.style,
+ title=self.highlighter(title),
+ border_style="blue",
+ height=height,
+ )
+
+
+class Splitter(ABC):
+ """Base class for a splitter."""
+
+ name: str = ""
+
+ @abstractmethod
+ def get_tree_icon(self) -> str:
+ """Get the icon (emoji) used in layout.tree"""
+
+ @abstractmethod
+ def divide(
+ self, children: Sequence["Layout"], region: Region
+ ) -> Iterable[Tuple["Layout", Region]]:
+ """Divide a region amongst several child layouts.
+
+ Args:
+ children (Sequence(Layout)): A number of child layouts.
+ region (Region): A rectangular region to divide.
+ """
+
+
+class RowSplitter(Splitter):
+ """Split a layout region in to rows."""
+
+ name = "row"
+
+ def get_tree_icon(self) -> str:
+ return "[layout.tree.row]⬌"
+
+ def divide(
+ self, children: Sequence["Layout"], region: Region
+ ) -> Iterable[Tuple["Layout", Region]]:
+ x, y, width, height = region
+ render_widths = ratio_resolve(width, children)
+ offset = 0
+ _Region = Region
+ for child, child_width in zip(children, render_widths):
+ yield child, _Region(x + offset, y, child_width, height)
+ offset += child_width
+
+
+class ColumnSplitter(Splitter):
+ """Split a layout region in to columns."""
+
+ name = "column"
+
+ def get_tree_icon(self) -> str:
+ return "[layout.tree.column]⬍"
+
+ def divide(
+ self, children: Sequence["Layout"], region: Region
+ ) -> Iterable[Tuple["Layout", Region]]:
+ x, y, width, height = region
+ render_heights = ratio_resolve(height, children)
+ offset = 0
+ _Region = Region
+ for child, child_height in zip(children, render_heights):
+ yield child, _Region(x, y + offset, width, child_height)
+ offset += child_height
+
+
+@rich_repr
+class Layout:
+ """A renderable to divide a fixed height in to rows or columns.
+
+ Args:
+ renderable (RenderableType, optional): Renderable content, or None for placeholder. Defaults to None.
+ name (str, optional): Optional identifier for Layout. Defaults to None.
+ size (int, optional): Optional fixed size of layout. Defaults to None.
+ minimum_size (int, optional): Minimum size of layout. Defaults to 1.
+ ratio (int, optional): Optional ratio for flexible layout. Defaults to 1.
+ visible (bool, optional): Visibility of layout. Defaults to True.
+ """
+
+ splitters = {"row": RowSplitter, "column": ColumnSplitter}
+
+ def __init__(
+ self,
+ renderable: Optional[RenderableType] = None,
+ *,
+ name: Optional[str] = None,
+ size: Optional[int] = None,
+ minimum_size: int = 1,
+ ratio: int = 1,
+ visible: bool = True,
+ ) -> None:
+ self._renderable = renderable or _Placeholder(self)
+ self.size = size
+ self.minimum_size = minimum_size
+ self.ratio = ratio
+ self.name = name
+ self.visible = visible
+ self.splitter: Splitter = self.splitters["column"]()
+ self._children: List[Layout] = []
+ self._render_map: RenderMap = {}
+ self._lock = RLock()
+
+ def __rich_repr__(self) -> Result:
+ yield "name", self.name, None
+ yield "size", self.size, None
+ yield "minimum_size", self.minimum_size, 1
+ yield "ratio", self.ratio, 1
+
+ @property
+ def renderable(self) -> RenderableType:
+ """Layout renderable."""
+ return self if self._children else self._renderable
+
+ @property
+ def children(self) -> List["Layout"]:
+ """Gets (visible) layout children."""
+ return [child for child in self._children if child.visible]
+
+ @property
+ def map(self) -> RenderMap:
+ """Get a map of the last render."""
+ return self._render_map
+
+ def get(self, name: str) -> Optional["Layout"]:
+ """Get a named layout, or None if it doesn't exist.
+
+ Args:
+ name (str): Name of layout.
+
+ Returns:
+ Optional[Layout]: Layout instance or None if no layout was found.
+ """
+ if self.name == name:
+ return self
+ else:
+ for child in self._children:
+ named_layout = child.get(name)
+ if named_layout is not None:
+ return named_layout
+ return None
+
+ def __getitem__(self, name: str) -> "Layout":
+ layout = self.get(name)
+ if layout is None:
+ raise KeyError(f"No layout with name {name!r}")
+ return layout
+
+ @property
+ def tree(self) -> "Tree":
+ """Get a tree renderable to show layout structure."""
+ from pip._vendor.rich.styled import Styled
+ from pip._vendor.rich.table import Table
+ from pip._vendor.rich.tree import Tree
+
+ def summary(layout: "Layout") -> Table:
+
+ icon = layout.splitter.get_tree_icon()
+
+ table = Table.grid(padding=(0, 1, 0, 0))
+
+ text: RenderableType = (
+ Pretty(layout) if layout.visible else Styled(Pretty(layout), "dim")
+ )
+ table.add_row(icon, text)
+ _summary = table
+ return _summary
+
+ layout = self
+ tree = Tree(
+ summary(layout),
+ guide_style=f"layout.tree.{layout.splitter.name}",
+ highlight=True,
+ )
+
+ def recurse(tree: "Tree", layout: "Layout") -> None:
+ for child in layout._children:
+ recurse(
+ tree.add(
+ summary(child),
+ guide_style=f"layout.tree.{child.splitter.name}",
+ ),
+ child,
+ )
+
+ recurse(tree, self)
+ return tree
+
+ def split(
+ self,
+ *layouts: Union["Layout", RenderableType],
+ splitter: Union[Splitter, str] = "column",
+ ) -> None:
+ """Split the layout in to multiple sub-layouts.
+
+ Args:
+ *layouts (Layout): Positional arguments should be (sub) Layout instances.
+ splitter (Union[Splitter, str]): Splitter instance or name of splitter.
+ """
+ _layouts = [
+ layout if isinstance(layout, Layout) else Layout(layout)
+ for layout in layouts
+ ]
+ try:
+ self.splitter = (
+ splitter
+ if isinstance(splitter, Splitter)
+ else self.splitters[splitter]()
+ )
+ except KeyError:
+ raise NoSplitter(f"No splitter called {splitter!r}")
+ self._children[:] = _layouts
+
+ def add_split(self, *layouts: Union["Layout", RenderableType]) -> None:
+ """Add a new layout(s) to existing split.
+
+ Args:
+ *layouts (Union[Layout, RenderableType]): Positional arguments should be renderables or (sub) Layout instances.
+
+ """
+ _layouts = (
+ layout if isinstance(layout, Layout) else Layout(layout)
+ for layout in layouts
+ )
+ self._children.extend(_layouts)
+
+ def split_row(self, *layouts: Union["Layout", RenderableType]) -> None:
+ """Split the layout in to a row (layouts side by side).
+
+ Args:
+ *layouts (Layout): Positional arguments should be (sub) Layout instances.
+ """
+ self.split(*layouts, splitter="row")
+
+ def split_column(self, *layouts: Union["Layout", RenderableType]) -> None:
+ """Split the layout in to a column (layouts stacked on top of each other).
+
+ Args:
+ *layouts (Layout): Positional arguments should be (sub) Layout instances.
+ """
+ self.split(*layouts, splitter="column")
+
+ def unsplit(self) -> None:
+ """Reset splits to initial state."""
+ del self._children[:]
+
+ def update(self, renderable: RenderableType) -> None:
+ """Update renderable.
+
+ Args:
+ renderable (RenderableType): New renderable object.
+ """
+ with self._lock:
+ self._renderable = renderable
+
+ def refresh_screen(self, console: "Console", layout_name: str) -> None:
+ """Refresh a sub-layout.
+
+ Args:
+ console (Console): Console instance where Layout is to be rendered.
+ layout_name (str): Name of layout.
+ """
+ with self._lock:
+ layout = self[layout_name]
+ region, _lines = self._render_map[layout]
+ (x, y, width, height) = region
+ lines = console.render_lines(
+ layout, console.options.update_dimensions(width, height)
+ )
+ self._render_map[layout] = LayoutRender(region, lines)
+ console.update_screen_lines(lines, x, y)
+
+ def _make_region_map(self, width: int, height: int) -> RegionMap:
+ """Create a dict that maps layout on to Region."""
+ stack: List[Tuple[Layout, Region]] = [(self, Region(0, 0, width, height))]
+ push = stack.append
+ pop = stack.pop
+ layout_regions: List[Tuple[Layout, Region]] = []
+ append_layout_region = layout_regions.append
+ while stack:
+ append_layout_region(pop())
+ layout, region = layout_regions[-1]
+ children = layout.children
+ if children:
+ for child_and_region in layout.splitter.divide(children, region):
+ push(child_and_region)
+
+ region_map = {
+ layout: region
+ for layout, region in sorted(layout_regions, key=itemgetter(1))
+ }
+ return region_map
+
+ def render(self, console: Console, options: ConsoleOptions) -> RenderMap:
+ """Render the sub_layouts.
+
+ Args:
+ console (Console): Console instance.
+ options (ConsoleOptions): Console options.
+
+ Returns:
+ RenderMap: A dict that maps Layout on to a tuple of Region, lines
+ """
+ render_width = options.max_width
+ render_height = options.height or console.height
+ region_map = self._make_region_map(render_width, render_height)
+ layout_regions = [
+ (layout, region)
+ for layout, region in region_map.items()
+ if not layout.children
+ ]
+ render_map: Dict["Layout", "LayoutRender"] = {}
+ render_lines = console.render_lines
+ update_dimensions = options.update_dimensions
+
+ for layout, region in layout_regions:
+ lines = render_lines(
+ layout.renderable, update_dimensions(region.width, region.height)
+ )
+ render_map[layout] = LayoutRender(region, lines)
+ return render_map
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ with self._lock:
+ width = options.max_width or console.width
+ height = options.height or console.height
+ render_map = self.render(console, options.update_dimensions(width, height))
+ self._render_map = render_map
+ layout_lines: List[List[Segment]] = [[] for _ in range(height)]
+ _islice = islice
+ for (region, lines) in render_map.values():
+ _x, y, _layout_width, layout_height = region
+ for row, line in zip(
+ _islice(layout_lines, y, y + layout_height), lines
+ ):
+ row.extend(line)
+
+ new_line = Segment.line()
+ for layout_row in layout_lines:
+ yield from layout_row
+ yield new_line
+
+
+if __name__ == "__main__":
+ from pip._vendor.rich.console import Console
+
+ console = Console()
+ layout = Layout()
+
+ layout.split_column(
+ Layout(name="header", size=3),
+ Layout(ratio=1, name="main"),
+ Layout(size=10, name="footer"),
+ )
+
+ layout["main"].split_row(Layout(name="side"), Layout(name="body", ratio=2))
+
+ layout["body"].split_row(Layout(name="content", ratio=2), Layout(name="s2"))
+
+ layout["s2"].split_column(
+ Layout(name="top"), Layout(name="middle"), Layout(name="bottom")
+ )
+
+ layout["side"].split_column(Layout(layout.tree, name="left1"), Layout(name="left2"))
+
+ layout["content"].update("foo")
+
+ console.print(layout)
diff --git a/src/pip/_vendor/rich/live.py b/src/pip/_vendor/rich/live.py
new file mode 100644
index 000000000..e635fe5c9
--- /dev/null
+++ b/src/pip/_vendor/rich/live.py
@@ -0,0 +1,373 @@
+import sys
+from threading import Event, RLock, Thread
+from types import TracebackType
+from typing import IO, Any, Callable, List, Optional, TextIO, Type, cast
+
+from . import get_console
+from .console import Console, ConsoleRenderable, RenderableType, RenderHook
+from .control import Control
+from .file_proxy import FileProxy
+from .jupyter import JupyterMixin
+from .live_render import LiveRender, VerticalOverflowMethod
+from .screen import Screen
+from .text import Text
+
+
+class _RefreshThread(Thread):
+ """A thread that calls refresh() at regular intervals."""
+
+ def __init__(self, live: "Live", refresh_per_second: float) -> None:
+ self.live = live
+ self.refresh_per_second = refresh_per_second
+ self.done = Event()
+ super().__init__(daemon=True)
+
+ def stop(self) -> None:
+ self.done.set()
+
+ def run(self) -> None:
+ while not self.done.wait(1 / self.refresh_per_second):
+ with self.live._lock:
+ if not self.done.is_set():
+ self.live.refresh()
+
+
+class Live(JupyterMixin, RenderHook):
+ """Renders an auto-updating live display of any given renderable.
+
+ Args:
+ renderable (RenderableType, optional): The renderable to live display. Defaults to displaying nothing.
+ console (Console, optional): Optional Console instance. Default will an internal Console instance writing to stdout.
+ screen (bool, optional): Enable alternate screen mode. Defaults to False.
+ auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()` or `update()` with refresh flag. Defaults to True
+ refresh_per_second (float, optional): Number of times per second to refresh the live display. Defaults to 4.
+ transient (bool, optional): Clear the renderable on exit (has no effect when screen=True). Defaults to False.
+ redirect_stdout (bool, optional): Enable redirection of stdout, so ``print`` may be used. Defaults to True.
+ redirect_stderr (bool, optional): Enable redirection of stderr. Defaults to True.
+ vertical_overflow (VerticalOverflowMethod, optional): How to handle renderable when it is too tall for the console. Defaults to "ellipsis".
+ get_renderable (Callable[[], RenderableType], optional): Optional callable to get renderable. Defaults to None.
+ """
+
+ def __init__(
+ self,
+ renderable: Optional[RenderableType] = None,
+ *,
+ console: Optional[Console] = None,
+ screen: bool = False,
+ auto_refresh: bool = True,
+ refresh_per_second: float = 4,
+ transient: bool = False,
+ redirect_stdout: bool = True,
+ redirect_stderr: bool = True,
+ vertical_overflow: VerticalOverflowMethod = "ellipsis",
+ get_renderable: Optional[Callable[[], RenderableType]] = None,
+ ) -> None:
+ assert refresh_per_second > 0, "refresh_per_second must be > 0"
+ self._renderable = renderable
+ self.console = console if console is not None else get_console()
+ self._screen = screen
+ self._alt_screen = False
+
+ self._redirect_stdout = redirect_stdout
+ self._redirect_stderr = redirect_stderr
+ self._restore_stdout: Optional[IO[str]] = None
+ self._restore_stderr: Optional[IO[str]] = None
+
+ self._lock = RLock()
+ self.ipy_widget: Optional[Any] = None
+ self.auto_refresh = auto_refresh
+ self._started: bool = False
+ self.transient = True if screen else transient
+
+ self._refresh_thread: Optional[_RefreshThread] = None
+ self.refresh_per_second = refresh_per_second
+
+ self.vertical_overflow = vertical_overflow
+ self._get_renderable = get_renderable
+ self._live_render = LiveRender(
+ self.get_renderable(), vertical_overflow=vertical_overflow
+ )
+
+ @property
+ def is_started(self) -> bool:
+ """Check if live display has been started."""
+ return self._started
+
+ def get_renderable(self) -> RenderableType:
+ renderable = (
+ self._get_renderable()
+ if self._get_renderable is not None
+ else self._renderable
+ )
+ return renderable or ""
+
+ def start(self, refresh: bool = False) -> None:
+ """Start live rendering display.
+
+ Args:
+ refresh (bool, optional): Also refresh. Defaults to False.
+ """
+ with self._lock:
+ if self._started:
+ return
+ self.console.set_live(self)
+ self._started = True
+ if self._screen:
+ self._alt_screen = self.console.set_alt_screen(True)
+ self.console.show_cursor(False)
+ self._enable_redirect_io()
+ self.console.push_render_hook(self)
+ if refresh:
+ try:
+ self.refresh()
+ except Exception:
+ # If refresh fails, we want to stop the redirection of sys.stderr,
+ # so the error stacktrace is properly displayed in the terminal.
+ # (or, if the code that calls Rich captures the exception and wants to display something,
+ # let this be displayed in the terminal).
+ self.stop()
+ raise
+ if self.auto_refresh:
+ self._refresh_thread = _RefreshThread(self, self.refresh_per_second)
+ self._refresh_thread.start()
+
+ def stop(self) -> None:
+ """Stop live rendering display."""
+ with self._lock:
+ if not self._started:
+ return
+ self.console.clear_live()
+ self._started = False
+
+ if self.auto_refresh and self._refresh_thread is not None:
+ self._refresh_thread.stop()
+ self._refresh_thread = None
+ # allow it to fully render on the last even if overflow
+ self.vertical_overflow = "visible"
+ with self.console:
+ try:
+ if not self._alt_screen and not self.console.is_jupyter:
+ self.refresh()
+ finally:
+ self._disable_redirect_io()
+ self.console.pop_render_hook()
+ if not self._alt_screen and self.console.is_terminal:
+ self.console.line()
+ self.console.show_cursor(True)
+ if self._alt_screen:
+ self.console.set_alt_screen(False)
+
+ if self.transient and not self._alt_screen:
+ self.console.control(self._live_render.restore_cursor())
+ if self.ipy_widget is not None and self.transient:
+ self.ipy_widget.close() # pragma: no cover
+
+ def __enter__(self) -> "Live":
+ self.start(refresh=self._renderable is not None)
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ self.stop()
+
+ def _enable_redirect_io(self) -> None:
+ """Enable redirecting of stdout / stderr."""
+ if self.console.is_terminal or self.console.is_jupyter:
+ if self._redirect_stdout and not isinstance(sys.stdout, FileProxy):
+ self._restore_stdout = sys.stdout
+ sys.stdout = cast("TextIO", FileProxy(self.console, sys.stdout))
+ if self._redirect_stderr and not isinstance(sys.stderr, FileProxy):
+ self._restore_stderr = sys.stderr
+ sys.stderr = cast("TextIO", FileProxy(self.console, sys.stderr))
+
+ def _disable_redirect_io(self) -> None:
+ """Disable redirecting of stdout / stderr."""
+ if self._restore_stdout:
+ sys.stdout = cast("TextIO", self._restore_stdout)
+ self._restore_stdout = None
+ if self._restore_stderr:
+ sys.stderr = cast("TextIO", self._restore_stderr)
+ self._restore_stderr = None
+
+ @property
+ def renderable(self) -> RenderableType:
+ """Get the renderable that is being displayed
+
+ Returns:
+ RenderableType: Displayed renderable.
+ """
+ renderable = self.get_renderable()
+ return Screen(renderable) if self._alt_screen else renderable
+
+ def update(self, renderable: RenderableType, *, refresh: bool = False) -> None:
+ """Update the renderable that is being displayed
+
+ Args:
+ renderable (RenderableType): New renderable to use.
+ refresh (bool, optional): Refresh the display. Defaults to False.
+ """
+ with self._lock:
+ self._renderable = renderable
+ if refresh:
+ self.refresh()
+
+ def refresh(self) -> None:
+ """Update the display of the Live Render."""
+ with self._lock:
+ self._live_render.set_renderable(self.renderable)
+ if self.console.is_jupyter: # pragma: no cover
+ try:
+ from IPython.display import display
+ from ipywidgets import Output
+ except ImportError:
+ import warnings
+
+ warnings.warn('install "ipywidgets" for Jupyter support')
+ else:
+ if self.ipy_widget is None:
+ self.ipy_widget = Output()
+ display(self.ipy_widget)
+
+ with self.ipy_widget:
+ self.ipy_widget.clear_output(wait=True)
+ self.console.print(self._live_render.renderable)
+ elif self.console.is_terminal and not self.console.is_dumb_terminal:
+ with self.console:
+ self.console.print(Control())
+ elif (
+ not self._started and not self.transient
+ ): # if it is finished allow files or dumb-terminals to see final result
+ with self.console:
+ self.console.print(Control())
+
+ def process_renderables(
+ self, renderables: List[ConsoleRenderable]
+ ) -> List[ConsoleRenderable]:
+ """Process renderables to restore cursor and display progress."""
+ self._live_render.vertical_overflow = self.vertical_overflow
+ if self.console.is_interactive:
+ # lock needs acquiring as user can modify live_render renderable at any time unlike in Progress.
+ with self._lock:
+ reset = (
+ Control.home()
+ if self._alt_screen
+ else self._live_render.position_cursor()
+ )
+ renderables = [reset, *renderables, self._live_render]
+ elif (
+ not self._started and not self.transient
+ ): # if it is finished render the final output for files or dumb_terminals
+ renderables = [*renderables, self._live_render]
+
+ return renderables
+
+
+if __name__ == "__main__": # pragma: no cover
+ import random
+ import time
+ from itertools import cycle
+ from typing import Dict, List, Tuple
+
+ from .align import Align
+ from .console import Console
+ from .live import Live as Live
+ from .panel import Panel
+ from .rule import Rule
+ from .syntax import Syntax
+ from .table import Table
+
+ console = Console()
+
+ syntax = Syntax(
+ '''def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
+ """Iterate and generate a tuple with a flag for last value."""
+ iter_values = iter(values)
+ try:
+ previous_value = next(iter_values)
+ except StopIteration:
+ return
+ for value in iter_values:
+ yield False, previous_value
+ previous_value = value
+ yield True, previous_value''',
+ "python",
+ line_numbers=True,
+ )
+
+ table = Table("foo", "bar", "baz")
+ table.add_row("1", "2", "3")
+
+ progress_renderables = [
+ "You can make the terminal shorter and taller to see the live table hide"
+ "Text may be printed while the progress bars are rendering.",
+ Panel("In fact, [i]any[/i] renderable will work"),
+ "Such as [magenta]tables[/]...",
+ table,
+ "Pretty printed structures...",
+ {"type": "example", "text": "Pretty printed"},
+ "Syntax...",
+ syntax,
+ Rule("Give it a try!"),
+ ]
+
+ examples = cycle(progress_renderables)
+
+ exchanges = [
+ "SGD",
+ "MYR",
+ "EUR",
+ "USD",
+ "AUD",
+ "JPY",
+ "CNH",
+ "HKD",
+ "CAD",
+ "INR",
+ "DKK",
+ "GBP",
+ "RUB",
+ "NZD",
+ "MXN",
+ "IDR",
+ "TWD",
+ "THB",
+ "VND",
+ ]
+ with Live(console=console) as live_table:
+ exchange_rate_dict: Dict[Tuple[str, str], float] = {}
+
+ for index in range(100):
+ select_exchange = exchanges[index % len(exchanges)]
+
+ for exchange in exchanges:
+ if exchange == select_exchange:
+ continue
+ time.sleep(0.4)
+ if random.randint(0, 10) < 1:
+ console.log(next(examples))
+ exchange_rate_dict[(select_exchange, exchange)] = 200 / (
+ (random.random() * 320) + 1
+ )
+ if len(exchange_rate_dict) > len(exchanges) - 1:
+ exchange_rate_dict.pop(list(exchange_rate_dict.keys())[0])
+ table = Table(title="Exchange Rates")
+
+ table.add_column("Source Currency")
+ table.add_column("Destination Currency")
+ table.add_column("Exchange Rate")
+
+ for ((source, dest), exchange_rate) in exchange_rate_dict.items():
+ table.add_row(
+ source,
+ dest,
+ Text(
+ f"{exchange_rate:.4f}",
+ style="red" if exchange_rate < 1.0 else "green",
+ ),
+ )
+
+ live_table.update(Align.center(table))
diff --git a/src/pip/_vendor/rich/live_render.py b/src/pip/_vendor/rich/live_render.py
new file mode 100644
index 000000000..b90fbf7f3
--- /dev/null
+++ b/src/pip/_vendor/rich/live_render.py
@@ -0,0 +1,113 @@
+import sys
+from typing import Optional, Tuple
+
+if sys.version_info >= (3, 8):
+ from typing import Literal
+else:
+ from pip._vendor.typing_extensions import Literal # pragma: no cover
+
+
+from ._loop import loop_last
+from .console import Console, ConsoleOptions, RenderableType, RenderResult
+from .control import Control
+from .segment import ControlType, Segment
+from .style import StyleType
+from .text import Text
+
+VerticalOverflowMethod = Literal["crop", "ellipsis", "visible"]
+
+
+class LiveRender:
+ """Creates a renderable that may be updated.
+
+ Args:
+ renderable (RenderableType): Any renderable object.
+ style (StyleType, optional): An optional style to apply to the renderable. Defaults to "".
+ """
+
+ def __init__(
+ self,
+ renderable: RenderableType,
+ style: StyleType = "",
+ vertical_overflow: VerticalOverflowMethod = "ellipsis",
+ ) -> None:
+ self.renderable = renderable
+ self.style = style
+ self.vertical_overflow = vertical_overflow
+ self._shape: Optional[Tuple[int, int]] = None
+
+ def set_renderable(self, renderable: RenderableType) -> None:
+ """Set a new renderable.
+
+ Args:
+ renderable (RenderableType): Any renderable object, including str.
+ """
+ self.renderable = renderable
+
+ def position_cursor(self) -> Control:
+ """Get control codes to move cursor to beginning of live render.
+
+ Returns:
+ Control: A control instance that may be printed.
+ """
+ if self._shape is not None:
+ _, height = self._shape
+ return Control(
+ ControlType.CARRIAGE_RETURN,
+ (ControlType.ERASE_IN_LINE, 2),
+ *(
+ (
+ (ControlType.CURSOR_UP, 1),
+ (ControlType.ERASE_IN_LINE, 2),
+ )
+ * (height - 1)
+ )
+ )
+ return Control()
+
+ def restore_cursor(self) -> Control:
+ """Get control codes to clear the render and restore the cursor to its previous position.
+
+ Returns:
+ Control: A Control instance that may be printed.
+ """
+ if self._shape is not None:
+ _, height = self._shape
+ return Control(
+ ControlType.CARRIAGE_RETURN,
+ *((ControlType.CURSOR_UP, 1), (ControlType.ERASE_IN_LINE, 2)) * height
+ )
+ return Control()
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+
+ renderable = self.renderable
+ style = console.get_style(self.style)
+ lines = console.render_lines(renderable, options, style=style, pad=False)
+ shape = Segment.get_shape(lines)
+
+ _, height = shape
+ if height > options.size.height:
+ if self.vertical_overflow == "crop":
+ lines = lines[: options.size.height]
+ shape = Segment.get_shape(lines)
+ elif self.vertical_overflow == "ellipsis":
+ lines = lines[: (options.size.height - 1)]
+ overflow_text = Text(
+ "...",
+ overflow="crop",
+ justify="center",
+ end="",
+ style="live.ellipsis",
+ )
+ lines.append(list(console.render(overflow_text)))
+ shape = Segment.get_shape(lines)
+ self._shape = shape
+
+ new_line = Segment.line()
+ for last, line in loop_last(lines):
+ yield from line
+ if not last:
+ yield new_line
diff --git a/src/pip/_vendor/rich/logging.py b/src/pip/_vendor/rich/logging.py
new file mode 100644
index 000000000..91368dda7
--- /dev/null
+++ b/src/pip/_vendor/rich/logging.py
@@ -0,0 +1,289 @@
+import logging
+from datetime import datetime
+from logging import Handler, LogRecord
+from pathlib import Path
+from types import ModuleType
+from typing import ClassVar, Iterable, List, Optional, Type, Union
+
+from pip._vendor.rich._null_file import NullFile
+
+from . import get_console
+from ._log_render import FormatTimeCallable, LogRender
+from .console import Console, ConsoleRenderable
+from .highlighter import Highlighter, ReprHighlighter
+from .text import Text
+from .traceback import Traceback
+
+
+class RichHandler(Handler):
+ """A logging handler that renders output with Rich. The time / level / message and file are displayed in columns.
+ The level is color coded, and the message is syntax highlighted.
+
+ Note:
+ Be careful when enabling console markup in log messages if you have configured logging for libraries not
+ under your control. If a dependency writes messages containing square brackets, it may not produce the intended output.
+
+ Args:
+ level (Union[int, str], optional): Log level. Defaults to logging.NOTSET.
+ console (:class:`~rich.console.Console`, optional): Optional console instance to write logs.
+ Default will use a global console instance writing to stdout.
+ show_time (bool, optional): Show a column for the time. Defaults to True.
+ omit_repeated_times (bool, optional): Omit repetition of the same time. Defaults to True.
+ show_level (bool, optional): Show a column for the level. Defaults to True.
+ show_path (bool, optional): Show the path to the original log call. Defaults to True.
+ enable_link_path (bool, optional): Enable terminal link of path column to file. Defaults to True.
+ highlighter (Highlighter, optional): Highlighter to style log messages, or None to use ReprHighlighter. Defaults to None.
+ markup (bool, optional): Enable console markup in log messages. Defaults to False.
+ rich_tracebacks (bool, optional): Enable rich tracebacks with syntax highlighting and formatting. Defaults to False.
+ tracebacks_width (Optional[int], optional): Number of characters used to render tracebacks, or None for full width. Defaults to None.
+ tracebacks_extra_lines (int, optional): Additional lines of code to render tracebacks, or None for full width. Defaults to None.
+ tracebacks_theme (str, optional): Override pygments theme used in traceback.
+ tracebacks_word_wrap (bool, optional): Enable word wrapping of long tracebacks lines. Defaults to True.
+ tracebacks_show_locals (bool, optional): Enable display of locals in tracebacks. Defaults to False.
+ tracebacks_suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
+ locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
+ Defaults to 10.
+ locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
+ log_time_format (Union[str, TimeFormatterCallable], optional): If ``log_time`` is enabled, either string for strftime or callable that formats the time. Defaults to "[%x %X] ".
+ keywords (List[str], optional): List of words to highlight instead of ``RichHandler.KEYWORDS``.
+ """
+
+ KEYWORDS: ClassVar[Optional[List[str]]] = [
+ "GET",
+ "POST",
+ "HEAD",
+ "PUT",
+ "DELETE",
+ "OPTIONS",
+ "TRACE",
+ "PATCH",
+ ]
+ HIGHLIGHTER_CLASS: ClassVar[Type[Highlighter]] = ReprHighlighter
+
+ def __init__(
+ self,
+ level: Union[int, str] = logging.NOTSET,
+ console: Optional[Console] = None,
+ *,
+ show_time: bool = True,
+ omit_repeated_times: bool = True,
+ show_level: bool = True,
+ show_path: bool = True,
+ enable_link_path: bool = True,
+ highlighter: Optional[Highlighter] = None,
+ markup: bool = False,
+ rich_tracebacks: bool = False,
+ tracebacks_width: Optional[int] = None,
+ tracebacks_extra_lines: int = 3,
+ tracebacks_theme: Optional[str] = None,
+ tracebacks_word_wrap: bool = True,
+ tracebacks_show_locals: bool = False,
+ tracebacks_suppress: Iterable[Union[str, ModuleType]] = (),
+ locals_max_length: int = 10,
+ locals_max_string: int = 80,
+ log_time_format: Union[str, FormatTimeCallable] = "[%x %X]",
+ keywords: Optional[List[str]] = None,
+ ) -> None:
+ super().__init__(level=level)
+ self.console = console or get_console()
+ self.highlighter = highlighter or self.HIGHLIGHTER_CLASS()
+ self._log_render = LogRender(
+ show_time=show_time,
+ show_level=show_level,
+ show_path=show_path,
+ time_format=log_time_format,
+ omit_repeated_times=omit_repeated_times,
+ level_width=None,
+ )
+ self.enable_link_path = enable_link_path
+ self.markup = markup
+ self.rich_tracebacks = rich_tracebacks
+ self.tracebacks_width = tracebacks_width
+ self.tracebacks_extra_lines = tracebacks_extra_lines
+ self.tracebacks_theme = tracebacks_theme
+ self.tracebacks_word_wrap = tracebacks_word_wrap
+ self.tracebacks_show_locals = tracebacks_show_locals
+ self.tracebacks_suppress = tracebacks_suppress
+ self.locals_max_length = locals_max_length
+ self.locals_max_string = locals_max_string
+ self.keywords = keywords
+
+ def get_level_text(self, record: LogRecord) -> Text:
+ """Get the level name from the record.
+
+ Args:
+ record (LogRecord): LogRecord instance.
+
+ Returns:
+ Text: A tuple of the style and level name.
+ """
+ level_name = record.levelname
+ level_text = Text.styled(
+ level_name.ljust(8), f"logging.level.{level_name.lower()}"
+ )
+ return level_text
+
+ def emit(self, record: LogRecord) -> None:
+ """Invoked by logging."""
+ message = self.format(record)
+ traceback = None
+ if (
+ self.rich_tracebacks
+ and record.exc_info
+ and record.exc_info != (None, None, None)
+ ):
+ exc_type, exc_value, exc_traceback = record.exc_info
+ assert exc_type is not None
+ assert exc_value is not None
+ traceback = Traceback.from_exception(
+ exc_type,
+ exc_value,
+ exc_traceback,
+ width=self.tracebacks_width,
+ extra_lines=self.tracebacks_extra_lines,
+ theme=self.tracebacks_theme,
+ word_wrap=self.tracebacks_word_wrap,
+ show_locals=self.tracebacks_show_locals,
+ locals_max_length=self.locals_max_length,
+ locals_max_string=self.locals_max_string,
+ suppress=self.tracebacks_suppress,
+ )
+ message = record.getMessage()
+ if self.formatter:
+ record.message = record.getMessage()
+ formatter = self.formatter
+ if hasattr(formatter, "usesTime") and formatter.usesTime():
+ record.asctime = formatter.formatTime(record, formatter.datefmt)
+ message = formatter.formatMessage(record)
+
+ message_renderable = self.render_message(record, message)
+ log_renderable = self.render(
+ record=record, traceback=traceback, message_renderable=message_renderable
+ )
+ if isinstance(self.console.file, NullFile):
+ # Handles pythonw, where stdout/stderr are null, and we return NullFile
+ # instance from Console.file. In this case, we still want to make a log record
+ # even though we won't be writing anything to a file.
+ self.handleError(record)
+ else:
+ try:
+ self.console.print(log_renderable)
+ except Exception:
+ self.handleError(record)
+
+ def render_message(self, record: LogRecord, message: str) -> "ConsoleRenderable":
+ """Render message text in to Text.
+
+ Args:
+ record (LogRecord): logging Record.
+ message (str): String containing log message.
+
+ Returns:
+ ConsoleRenderable: Renderable to display log message.
+ """
+ use_markup = getattr(record, "markup", self.markup)
+ message_text = Text.from_markup(message) if use_markup else Text(message)
+
+ highlighter = getattr(record, "highlighter", self.highlighter)
+ if highlighter:
+ message_text = highlighter(message_text)
+
+ if self.keywords is None:
+ self.keywords = self.KEYWORDS
+
+ if self.keywords:
+ message_text.highlight_words(self.keywords, "logging.keyword")
+
+ return message_text
+
+ def render(
+ self,
+ *,
+ record: LogRecord,
+ traceback: Optional[Traceback],
+ message_renderable: "ConsoleRenderable",
+ ) -> "ConsoleRenderable":
+ """Render log for display.
+
+ Args:
+ record (LogRecord): logging Record.
+ traceback (Optional[Traceback]): Traceback instance or None for no Traceback.
+ message_renderable (ConsoleRenderable): Renderable (typically Text) containing log message contents.
+
+ Returns:
+ ConsoleRenderable: Renderable to display log.
+ """
+ path = Path(record.pathname).name
+ level = self.get_level_text(record)
+ time_format = None if self.formatter is None else self.formatter.datefmt
+ log_time = datetime.fromtimestamp(record.created)
+
+ log_renderable = self._log_render(
+ self.console,
+ [message_renderable] if not traceback else [message_renderable, traceback],
+ log_time=log_time,
+ time_format=time_format,
+ level=level,
+ path=path,
+ line_no=record.lineno,
+ link_path=record.pathname if self.enable_link_path else None,
+ )
+ return log_renderable
+
+
+if __name__ == "__main__": # pragma: no cover
+ from time import sleep
+
+ FORMAT = "%(message)s"
+ # FORMAT = "%(asctime)-15s - %(levelname)s - %(message)s"
+ logging.basicConfig(
+ level="NOTSET",
+ format=FORMAT,
+ datefmt="[%X]",
+ handlers=[RichHandler(rich_tracebacks=True, tracebacks_show_locals=True)],
+ )
+ log = logging.getLogger("rich")
+
+ log.info("Server starting...")
+ log.info("Listening on http://127.0.0.1:8080")
+ sleep(1)
+
+ log.info("GET /index.html 200 1298")
+ log.info("GET /imgs/backgrounds/back1.jpg 200 54386")
+ log.info("GET /css/styles.css 200 54386")
+ log.warning("GET /favicon.ico 404 242")
+ sleep(1)
+
+ log.debug(
+ "JSONRPC request\n--> %r\n<-- %r",
+ {
+ "version": "1.1",
+ "method": "confirmFruitPurchase",
+ "params": [["apple", "orange", "mangoes", "pomelo"], 1.123],
+ "id": "194521489",
+ },
+ {"version": "1.1", "result": True, "error": None, "id": "194521489"},
+ )
+ log.debug(
+ "Loading configuration file /adasd/asdasd/qeqwe/qwrqwrqwr/sdgsdgsdg/werwerwer/dfgerert/ertertert/ertetert/werwerwer"
+ )
+ log.error("Unable to find 'pomelo' in database!")
+ log.info("POST /jsonrpc/ 200 65532")
+ log.info("POST /admin/ 401 42234")
+ log.warning("password was rejected for admin site.")
+
+ def divide() -> None:
+ number = 1
+ divisor = 0
+ foos = ["foo"] * 100
+ log.debug("in divide")
+ try:
+ number / divisor
+ except:
+ log.exception("An error of some kind occurred!")
+
+ divide()
+ sleep(1)
+ log.critical("Out of memory!")
+ log.info("Server exited with code=-1")
+ log.info("[bold]EXITING...[/bold]", extra=dict(markup=True))
diff --git a/src/pip/_vendor/rich/markup.py b/src/pip/_vendor/rich/markup.py
new file mode 100644
index 000000000..fd80d8c11
--- /dev/null
+++ b/src/pip/_vendor/rich/markup.py
@@ -0,0 +1,246 @@
+import re
+from ast import literal_eval
+from operator import attrgetter
+from typing import Callable, Iterable, List, Match, NamedTuple, Optional, Tuple, Union
+
+from ._emoji_replace import _emoji_replace
+from .emoji import EmojiVariant
+from .errors import MarkupError
+from .style import Style
+from .text import Span, Text
+
+RE_TAGS = re.compile(
+ r"""((\\*)\[([a-z#/@][^[]*?)])""",
+ re.VERBOSE,
+)
+
+RE_HANDLER = re.compile(r"^([\w.]*?)(\(.*?\))?$")
+
+
+class Tag(NamedTuple):
+ """A tag in console markup."""
+
+ name: str
+ """The tag name. e.g. 'bold'."""
+ parameters: Optional[str]
+ """Any additional parameters after the name."""
+
+ def __str__(self) -> str:
+ return (
+ self.name if self.parameters is None else f"{self.name} {self.parameters}"
+ )
+
+ @property
+ def markup(self) -> str:
+ """Get the string representation of this tag."""
+ return (
+ f"[{self.name}]"
+ if self.parameters is None
+ else f"[{self.name}={self.parameters}]"
+ )
+
+
+_ReStringMatch = Match[str] # regex match object
+_ReSubCallable = Callable[[_ReStringMatch], str] # Callable invoked by re.sub
+_EscapeSubMethod = Callable[[_ReSubCallable, str], str] # Sub method of a compiled re
+
+
+def escape(
+ markup: str,
+ _escape: _EscapeSubMethod = re.compile(r"(\\*)(\[[a-z#/@][^[]*?])").sub,
+) -> str:
+ """Escapes text so that it won't be interpreted as markup.
+
+ Args:
+ markup (str): Content to be inserted in to markup.
+
+ Returns:
+ str: Markup with square brackets escaped.
+ """
+
+ def escape_backslashes(match: Match[str]) -> str:
+ """Called by re.sub replace matches."""
+ backslashes, text = match.groups()
+ return f"{backslashes}{backslashes}\\{text}"
+
+ markup = _escape(escape_backslashes, markup)
+ return markup
+
+
+def _parse(markup: str) -> Iterable[Tuple[int, Optional[str], Optional[Tag]]]:
+ """Parse markup in to an iterable of tuples of (position, text, tag).
+
+ Args:
+ markup (str): A string containing console markup
+
+ """
+ position = 0
+ _divmod = divmod
+ _Tag = Tag
+ for match in RE_TAGS.finditer(markup):
+ full_text, escapes, tag_text = match.groups()
+ start, end = match.span()
+ if start > position:
+ yield start, markup[position:start], None
+ if escapes:
+ backslashes, escaped = _divmod(len(escapes), 2)
+ if backslashes:
+ # Literal backslashes
+ yield start, "\\" * backslashes, None
+ start += backslashes * 2
+ if escaped:
+ # Escape of tag
+ yield start, full_text[len(escapes) :], None
+ position = end
+ continue
+ text, equals, parameters = tag_text.partition("=")
+ yield start, None, _Tag(text, parameters if equals else None)
+ position = end
+ if position < len(markup):
+ yield position, markup[position:], None
+
+
+def render(
+ markup: str,
+ style: Union[str, Style] = "",
+ emoji: bool = True,
+ emoji_variant: Optional[EmojiVariant] = None,
+) -> Text:
+ """Render console markup in to a Text instance.
+
+ Args:
+ markup (str): A string containing console markup.
+ emoji (bool, optional): Also render emoji code. Defaults to True.
+
+ Raises:
+ MarkupError: If there is a syntax error in the markup.
+
+ Returns:
+ Text: A test instance.
+ """
+ emoji_replace = _emoji_replace
+ if "[" not in markup:
+ return Text(
+ emoji_replace(markup, default_variant=emoji_variant) if emoji else markup,
+ style=style,
+ )
+ text = Text(style=style)
+ append = text.append
+ normalize = Style.normalize
+
+ style_stack: List[Tuple[int, Tag]] = []
+ pop = style_stack.pop
+
+ spans: List[Span] = []
+ append_span = spans.append
+
+ _Span = Span
+ _Tag = Tag
+
+ def pop_style(style_name: str) -> Tuple[int, Tag]:
+ """Pop tag matching given style name."""
+ for index, (_, tag) in enumerate(reversed(style_stack), 1):
+ if tag.name == style_name:
+ return pop(-index)
+ raise KeyError(style_name)
+
+ for position, plain_text, tag in _parse(markup):
+ if plain_text is not None:
+ # Handle open brace escapes, where the brace is not part of a tag.
+ plain_text = plain_text.replace("\\[", "[")
+ append(emoji_replace(plain_text) if emoji else plain_text)
+ elif tag is not None:
+ if tag.name.startswith("/"): # Closing tag
+ style_name = tag.name[1:].strip()
+
+ if style_name: # explicit close
+ style_name = normalize(style_name)
+ try:
+ start, open_tag = pop_style(style_name)
+ except KeyError:
+ raise MarkupError(
+ f"closing tag '{tag.markup}' at position {position} doesn't match any open tag"
+ ) from None
+ else: # implicit close
+ try:
+ start, open_tag = pop()
+ except IndexError:
+ raise MarkupError(
+ f"closing tag '[/]' at position {position} has nothing to close"
+ ) from None
+
+ if open_tag.name.startswith("@"):
+ if open_tag.parameters:
+ handler_name = ""
+ parameters = open_tag.parameters.strip()
+ handler_match = RE_HANDLER.match(parameters)
+ if handler_match is not None:
+ handler_name, match_parameters = handler_match.groups()
+ parameters = (
+ "()" if match_parameters is None else match_parameters
+ )
+
+ try:
+ meta_params = literal_eval(parameters)
+ except SyntaxError as error:
+ raise MarkupError(
+ f"error parsing {parameters!r} in {open_tag.parameters!r}; {error.msg}"
+ )
+ except Exception as error:
+ raise MarkupError(
+ f"error parsing {open_tag.parameters!r}; {error}"
+ ) from None
+
+ if handler_name:
+ meta_params = (
+ handler_name,
+ meta_params
+ if isinstance(meta_params, tuple)
+ else (meta_params,),
+ )
+
+ else:
+ meta_params = ()
+
+ append_span(
+ _Span(
+ start, len(text), Style(meta={open_tag.name: meta_params})
+ )
+ )
+ else:
+ append_span(_Span(start, len(text), str(open_tag)))
+
+ else: # Opening tag
+ normalized_tag = _Tag(normalize(tag.name), tag.parameters)
+ style_stack.append((len(text), normalized_tag))
+
+ text_length = len(text)
+ while style_stack:
+ start, tag = style_stack.pop()
+ style = str(tag)
+ if style:
+ append_span(_Span(start, text_length, style))
+
+ text.spans = sorted(spans[::-1], key=attrgetter("start"))
+ return text
+
+
+if __name__ == "__main__": # pragma: no cover
+
+ MARKUP = [
+ "[red]Hello World[/red]",
+ "[magenta]Hello [b]World[/b]",
+ "[bold]Bold[italic] bold and italic [/bold]italic[/italic]",
+ "Click [link=https://www.willmcgugan.com]here[/link] to visit my Blog",
+ ":warning-emoji: [bold red blink] DANGER![/]",
+ ]
+
+ from pip._vendor.rich import print
+ from pip._vendor.rich.table import Table
+
+ grid = Table("Markup", "Result", padding=(0, 1))
+
+ for markup in MARKUP:
+ grid.add_row(Text(markup), markup)
+
+ print(grid)
diff --git a/src/pip/_vendor/rich/measure.py b/src/pip/_vendor/rich/measure.py
new file mode 100644
index 000000000..a508ffa80
--- /dev/null
+++ b/src/pip/_vendor/rich/measure.py
@@ -0,0 +1,151 @@
+from operator import itemgetter
+from typing import TYPE_CHECKING, Callable, NamedTuple, Optional, Sequence
+
+from . import errors
+from .protocol import is_renderable, rich_cast
+
+if TYPE_CHECKING:
+ from .console import Console, ConsoleOptions, RenderableType
+
+
+class Measurement(NamedTuple):
+ """Stores the minimum and maximum widths (in characters) required to render an object."""
+
+ minimum: int
+ """Minimum number of cells required to render."""
+ maximum: int
+ """Maximum number of cells required to render."""
+
+ @property
+ def span(self) -> int:
+ """Get difference between maximum and minimum."""
+ return self.maximum - self.minimum
+
+ def normalize(self) -> "Measurement":
+ """Get measurement that ensures that minimum <= maximum and minimum >= 0
+
+ Returns:
+ Measurement: A normalized measurement.
+ """
+ minimum, maximum = self
+ minimum = min(max(0, minimum), maximum)
+ return Measurement(max(0, minimum), max(0, max(minimum, maximum)))
+
+ def with_maximum(self, width: int) -> "Measurement":
+ """Get a RenderableWith where the widths are <= width.
+
+ Args:
+ width (int): Maximum desired width.
+
+ Returns:
+ Measurement: New Measurement object.
+ """
+ minimum, maximum = self
+ return Measurement(min(minimum, width), min(maximum, width))
+
+ def with_minimum(self, width: int) -> "Measurement":
+ """Get a RenderableWith where the widths are >= width.
+
+ Args:
+ width (int): Minimum desired width.
+
+ Returns:
+ Measurement: New Measurement object.
+ """
+ minimum, maximum = self
+ width = max(0, width)
+ return Measurement(max(minimum, width), max(maximum, width))
+
+ def clamp(
+ self, min_width: Optional[int] = None, max_width: Optional[int] = None
+ ) -> "Measurement":
+ """Clamp a measurement within the specified range.
+
+ Args:
+ min_width (int): Minimum desired width, or ``None`` for no minimum. Defaults to None.
+ max_width (int): Maximum desired width, or ``None`` for no maximum. Defaults to None.
+
+ Returns:
+ Measurement: New Measurement object.
+ """
+ measurement = self
+ if min_width is not None:
+ measurement = measurement.with_minimum(min_width)
+ if max_width is not None:
+ measurement = measurement.with_maximum(max_width)
+ return measurement
+
+ @classmethod
+ def get(
+ cls, console: "Console", options: "ConsoleOptions", renderable: "RenderableType"
+ ) -> "Measurement":
+ """Get a measurement for a renderable.
+
+ Args:
+ console (~rich.console.Console): Console instance.
+ options (~rich.console.ConsoleOptions): Console options.
+ renderable (RenderableType): An object that may be rendered with Rich.
+
+ Raises:
+ errors.NotRenderableError: If the object is not renderable.
+
+ Returns:
+ Measurement: Measurement object containing range of character widths required to render the object.
+ """
+ _max_width = options.max_width
+ if _max_width < 1:
+ return Measurement(0, 0)
+ if isinstance(renderable, str):
+ renderable = console.render_str(
+ renderable, markup=options.markup, highlight=False
+ )
+ renderable = rich_cast(renderable)
+ if is_renderable(renderable):
+ get_console_width: Optional[
+ Callable[["Console", "ConsoleOptions"], "Measurement"]
+ ] = getattr(renderable, "__rich_measure__", None)
+ if get_console_width is not None:
+ render_width = (
+ get_console_width(console, options)
+ .normalize()
+ .with_maximum(_max_width)
+ )
+ if render_width.maximum < 1:
+ return Measurement(0, 0)
+ return render_width.normalize()
+ else:
+ return Measurement(0, _max_width)
+ else:
+ raise errors.NotRenderableError(
+ f"Unable to get render width for {renderable!r}; "
+ "a str, Segment, or object with __rich_console__ method is required"
+ )
+
+
+def measure_renderables(
+ console: "Console",
+ options: "ConsoleOptions",
+ renderables: Sequence["RenderableType"],
+) -> "Measurement":
+ """Get a measurement that would fit a number of renderables.
+
+ Args:
+ console (~rich.console.Console): Console instance.
+ options (~rich.console.ConsoleOptions): Console options.
+ renderables (Iterable[RenderableType]): One or more renderable objects.
+
+ Returns:
+ Measurement: Measurement object containing range of character widths required to
+ contain all given renderables.
+ """
+ if not renderables:
+ return Measurement(0, 0)
+ get_measurement = Measurement.get
+ measurements = [
+ get_measurement(console, options, renderable) for renderable in renderables
+ ]
+ measured_width = Measurement(
+ max(measurements, key=itemgetter(0)).minimum,
+ max(measurements, key=itemgetter(1)).maximum,
+ )
+ return measured_width
diff --git a/src/pip/_vendor/rich/padding.py b/src/pip/_vendor/rich/padding.py
new file mode 100644
index 000000000..1b2204f59
--- /dev/null
+++ b/src/pip/_vendor/rich/padding.py
@@ -0,0 +1,141 @@
+from typing import cast, List, Optional, Tuple, TYPE_CHECKING, Union
+
+if TYPE_CHECKING:
+ from .console import (
+ Console,
+ ConsoleOptions,
+ RenderableType,
+ RenderResult,
+ )
+from .jupyter import JupyterMixin
+from .measure import Measurement
+from .style import Style
+from .segment import Segment
+
+
+PaddingDimensions = Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]
+
+
+class Padding(JupyterMixin):
+ """Draw space around content.
+
+ Example:
+ >>> print(Padding("Hello", (2, 4), style="on blue"))
+
+ Args:
+ renderable (RenderableType): String or other renderable.
+ pad (Union[int, Tuple[int]]): Padding for top, right, bottom, and left borders.
+ May be specified with 1, 2, or 4 integers (CSS style).
+ style (Union[str, Style], optional): Style for padding characters. Defaults to "none".
+ expand (bool, optional): Expand padding to fit available width. Defaults to True.
+ """
+
+ def __init__(
+ self,
+ renderable: "RenderableType",
+ pad: "PaddingDimensions" = (0, 0, 0, 0),
+ *,
+ style: Union[str, Style] = "none",
+ expand: bool = True,
+ ):
+ self.renderable = renderable
+ self.top, self.right, self.bottom, self.left = self.unpack(pad)
+ self.style = style
+ self.expand = expand
+
+ @classmethod
+ def indent(cls, renderable: "RenderableType", level: int) -> "Padding":
+ """Make padding instance to render an indent.
+
+ Args:
+ renderable (RenderableType): String or other renderable.
+ level (int): Number of characters to indent.
+
+ Returns:
+ Padding: A Padding instance.
+ """
+
+ return Padding(renderable, pad=(0, 0, 0, level), expand=False)
+
+ @staticmethod
+ def unpack(pad: "PaddingDimensions") -> Tuple[int, int, int, int]:
+ """Unpack padding specified in CSS style."""
+ if isinstance(pad, int):
+ return (pad, pad, pad, pad)
+ if len(pad) == 1:
+ _pad = pad[0]
+ return (_pad, _pad, _pad, _pad)
+ if len(pad) == 2:
+ pad_top, pad_right = cast(Tuple[int, int], pad)
+ return (pad_top, pad_right, pad_top, pad_right)
+ if len(pad) == 4:
+ top, right, bottom, left = cast(Tuple[int, int, int, int], pad)
+ return (top, right, bottom, left)
+ raise ValueError(f"1, 2 or 4 integers required for padding; {len(pad)} given")
+
+ def __repr__(self) -> str:
+ return f"Padding({self.renderable!r}, ({self.top},{self.right},{self.bottom},{self.left}))"
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ style = console.get_style(self.style)
+ if self.expand:
+ width = options.max_width
+ else:
+ width = min(
+ Measurement.get(console, options, self.renderable).maximum
+ + self.left
+ + self.right,
+ options.max_width,
+ )
+ render_options = options.update_width(width - self.left - self.right)
+ if render_options.height is not None:
+ render_options = render_options.update_height(
+ height=render_options.height - self.top - self.bottom
+ )
+ lines = console.render_lines(
+ self.renderable, render_options, style=style, pad=True
+ )
+ _Segment = Segment
+
+ left = _Segment(" " * self.left, style) if self.left else None
+ right = (
+ [_Segment(f'{" " * self.right}', style), _Segment.line()]
+ if self.right
+ else [_Segment.line()]
+ )
+ blank_line: Optional[List[Segment]] = None
+ if self.top:
+ blank_line = [_Segment(f'{" " * width}\n', style)]
+ yield from blank_line * self.top
+ if left:
+ for line in lines:
+ yield left
+ yield from line
+ yield from right
+ else:
+ for line in lines:
+ yield from line
+ yield from right
+ if self.bottom:
+ blank_line = blank_line or [_Segment(f'{" " * width}\n', style)]
+ yield from blank_line * self.bottom
+
+ def __rich_measure__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "Measurement":
+ max_width = options.max_width
+ extra_width = self.left + self.right
+ if max_width - extra_width < 1:
+ return Measurement(max_width, max_width)
+ measure_min, measure_max = Measurement.get(console, options, self.renderable)
+ measurement = Measurement(measure_min + extra_width, measure_max + extra_width)
+ measurement = measurement.with_maximum(max_width)
+ return measurement
+
+
+if __name__ == "__main__": # pragma: no cover
+ from pip._vendor.rich import print
+
+ print(Padding("Hello, World", (2, 4), style="on blue"))
diff --git a/src/pip/_vendor/rich/pager.py b/src/pip/_vendor/rich/pager.py
new file mode 100644
index 000000000..a3f7aa62a
--- /dev/null
+++ b/src/pip/_vendor/rich/pager.py
@@ -0,0 +1,34 @@
+from abc import ABC, abstractmethod
+from typing import Any
+
+
+class Pager(ABC):
+ """Base class for a pager."""
+
+ @abstractmethod
+ def show(self, content: str) -> None:
+ """Show content in pager.
+
+ Args:
+ content (str): Content to be displayed.
+ """
+
+
+class SystemPager(Pager):
+ """Uses the pager installed on the system."""
+
+ def _pager(self, content: str) -> Any: #  pragma: no cover
+ return __import__("pydoc").pager(content)
+
+ def show(self, content: str) -> None:
+ """Use the same pager used by pydoc."""
+ self._pager(content)
+
+
+if __name__ == "__main__": # pragma: no cover
+ from .__main__ import make_test_card
+ from .console import Console
+
+ console = Console()
+ with console.pager(styles=True):
+ console.print(make_test_card())
diff --git a/src/pip/_vendor/rich/palette.py b/src/pip/_vendor/rich/palette.py
new file mode 100644
index 000000000..fa0c4dd40
--- /dev/null
+++ b/src/pip/_vendor/rich/palette.py
@@ -0,0 +1,100 @@
+from math import sqrt
+from functools import lru_cache
+from typing import Sequence, Tuple, TYPE_CHECKING
+
+from .color_triplet import ColorTriplet
+
+if TYPE_CHECKING:
+ from pip._vendor.rich.table import Table
+
+
+class Palette:
+ """A palette of available colors."""
+
+ def __init__(self, colors: Sequence[Tuple[int, int, int]]):
+ self._colors = colors
+
+ def __getitem__(self, number: int) -> ColorTriplet:
+ return ColorTriplet(*self._colors[number])
+
+ def __rich__(self) -> "Table":
+ from pip._vendor.rich.color import Color
+ from pip._vendor.rich.style import Style
+ from pip._vendor.rich.text import Text
+ from pip._vendor.rich.table import Table
+
+ table = Table(
+ "index",
+ "RGB",
+ "Color",
+ title="Palette",
+ caption=f"{len(self._colors)} colors",
+ highlight=True,
+ caption_justify="right",
+ )
+ for index, color in enumerate(self._colors):
+ table.add_row(
+ str(index),
+ repr(color),
+ Text(" " * 16, style=Style(bgcolor=Color.from_rgb(*color))),
+ )
+ return table
+
+ # This is somewhat inefficient and needs caching
+ @lru_cache(maxsize=1024)
+ def match(self, color: Tuple[int, int, int]) -> int:
+ """Find a color from a palette that most closely matches a given color.
+
+ Args:
+ color (Tuple[int, int, int]): RGB components in range 0 > 255.
+
+ Returns:
+ int: Index of closes matching color.
+ """
+ red1, green1, blue1 = color
+ _sqrt = sqrt
+ get_color = self._colors.__getitem__
+
+ def get_color_distance(index: int) -> float:
+ """Get the distance to a color."""
+ red2, green2, blue2 = get_color(index)
+ red_mean = (red1 + red2) // 2
+ red = red1 - red2
+ green = green1 - green2
+ blue = blue1 - blue2
+ return _sqrt(
+ (((512 + red_mean) * red * red) >> 8)
+ + 4 * green * green
+ + (((767 - red_mean) * blue * blue) >> 8)
+ )
+
+ min_index = min(range(len(self._colors)), key=get_color_distance)
+ return min_index
+
+
+if __name__ == "__main__": # pragma: no cover
+ import colorsys
+ from typing import Iterable
+ from pip._vendor.rich.color import Color
+ from pip._vendor.rich.console import Console, ConsoleOptions
+ from pip._vendor.rich.segment import Segment
+ from pip._vendor.rich.style import Style
+
+ class ColorBox:
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> Iterable[Segment]:
+ height = console.size.height - 3
+ for y in range(0, height):
+ for x in range(options.max_width):
+ h = x / options.max_width
+ l = y / (height + 1)
+ r1, g1, b1 = colorsys.hls_to_rgb(h, l, 1.0)
+ r2, g2, b2 = colorsys.hls_to_rgb(h, l + (1 / height / 2), 1.0)
+ bgcolor = Color.from_rgb(r1 * 255, g1 * 255, b1 * 255)
+ color = Color.from_rgb(r2 * 255, g2 * 255, b2 * 255)
+ yield Segment("▄", Style(color=color, bgcolor=bgcolor))
+ yield Segment.line()
+
+ console = Console()
+ console.print(ColorBox())
diff --git a/src/pip/_vendor/rich/panel.py b/src/pip/_vendor/rich/panel.py
new file mode 100644
index 000000000..d522d80b5
--- /dev/null
+++ b/src/pip/_vendor/rich/panel.py
@@ -0,0 +1,308 @@
+from typing import TYPE_CHECKING, Optional
+
+from .align import AlignMethod
+from .box import ROUNDED, Box
+from .cells import cell_len
+from .jupyter import JupyterMixin
+from .measure import Measurement, measure_renderables
+from .padding import Padding, PaddingDimensions
+from .segment import Segment
+from .style import Style, StyleType
+from .text import Text, TextType
+
+if TYPE_CHECKING:
+ from .console import Console, ConsoleOptions, RenderableType, RenderResult
+
+
+class Panel(JupyterMixin):
+ """A console renderable that draws a border around its contents.
+
+ Example:
+ >>> console.print(Panel("Hello, World!"))
+
+ Args:
+ renderable (RenderableType): A console renderable object.
+ box (Box, optional): A Box instance that defines the look of the border (see :ref:`appendix_box`.
+ Defaults to box.ROUNDED.
+ safe_box (bool, optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
+ expand (bool, optional): If True the panel will stretch to fill the console
+ width, otherwise it will be sized to fit the contents. Defaults to True.
+ style (str, optional): The style of the panel (border and contents). Defaults to "none".
+ border_style (str, optional): The style of the border. Defaults to "none".
+ width (Optional[int], optional): Optional width of panel. Defaults to None to auto-detect.
+ height (Optional[int], optional): Optional height of panel. Defaults to None to auto-detect.
+ padding (Optional[PaddingDimensions]): Optional padding around renderable. Defaults to 0.
+ highlight (bool, optional): Enable automatic highlighting of panel title (if str). Defaults to False.
+ """
+
+ def __init__(
+ self,
+ renderable: "RenderableType",
+ box: Box = ROUNDED,
+ *,
+ title: Optional[TextType] = None,
+ title_align: AlignMethod = "center",
+ subtitle: Optional[TextType] = None,
+ subtitle_align: AlignMethod = "center",
+ safe_box: Optional[bool] = None,
+ expand: bool = True,
+ style: StyleType = "none",
+ border_style: StyleType = "none",
+ width: Optional[int] = None,
+ height: Optional[int] = None,
+ padding: PaddingDimensions = (0, 1),
+ highlight: bool = False,
+ ) -> None:
+ self.renderable = renderable
+ self.box = box
+ self.title = title
+ self.title_align: AlignMethod = title_align
+ self.subtitle = subtitle
+ self.subtitle_align = subtitle_align
+ self.safe_box = safe_box
+ self.expand = expand
+ self.style = style
+ self.border_style = border_style
+ self.width = width
+ self.height = height
+ self.padding = padding
+ self.highlight = highlight
+
+ @classmethod
+ def fit(
+ cls,
+ renderable: "RenderableType",
+ box: Box = ROUNDED,
+ *,
+ title: Optional[TextType] = None,
+ title_align: AlignMethod = "center",
+ subtitle: Optional[TextType] = None,
+ subtitle_align: AlignMethod = "center",
+ safe_box: Optional[bool] = None,
+ style: StyleType = "none",
+ border_style: StyleType = "none",
+ width: Optional[int] = None,
+ padding: PaddingDimensions = (0, 1),
+ ) -> "Panel":
+ """An alternative constructor that sets expand=False."""
+ return cls(
+ renderable,
+ box,
+ title=title,
+ title_align=title_align,
+ subtitle=subtitle,
+ subtitle_align=subtitle_align,
+ safe_box=safe_box,
+ style=style,
+ border_style=border_style,
+ width=width,
+ padding=padding,
+ expand=False,
+ )
+
+ @property
+ def _title(self) -> Optional[Text]:
+ if self.title:
+ title_text = (
+ Text.from_markup(self.title)
+ if isinstance(self.title, str)
+ else self.title.copy()
+ )
+ title_text.end = ""
+ title_text.plain = title_text.plain.replace("\n", " ")
+ title_text.no_wrap = True
+ title_text.expand_tabs()
+ title_text.pad(1)
+ return title_text
+ return None
+
+ @property
+ def _subtitle(self) -> Optional[Text]:
+ if self.subtitle:
+ subtitle_text = (
+ Text.from_markup(self.subtitle)
+ if isinstance(self.subtitle, str)
+ else self.subtitle.copy()
+ )
+ subtitle_text.end = ""
+ subtitle_text.plain = subtitle_text.plain.replace("\n", " ")
+ subtitle_text.no_wrap = True
+ subtitle_text.expand_tabs()
+ subtitle_text.pad(1)
+ return subtitle_text
+ return None
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ _padding = Padding.unpack(self.padding)
+ renderable = (
+ Padding(self.renderable, _padding) if any(_padding) else self.renderable
+ )
+ style = console.get_style(self.style)
+ border_style = style + console.get_style(self.border_style)
+ width = (
+ options.max_width
+ if self.width is None
+ else min(options.max_width, self.width)
+ )
+
+ safe_box: bool = console.safe_box if self.safe_box is None else self.safe_box
+ box = self.box.substitute(options, safe=safe_box)
+
+ def align_text(
+ text: Text, width: int, align: str, character: str, style: Style
+ ) -> Text:
+ """Gets new aligned text.
+
+ Args:
+ text (Text): Title or subtitle text.
+ width (int): Desired width.
+ align (str): Alignment.
+ character (str): Character for alignment.
+ style (Style): Border style
+
+ Returns:
+ Text: New text instance
+ """
+ text = text.copy()
+ text.truncate(width)
+ excess_space = width - cell_len(text.plain)
+ if excess_space:
+ if align == "left":
+ return Text.assemble(
+ text,
+ (character * excess_space, style),
+ no_wrap=True,
+ end="",
+ )
+ elif align == "center":
+ left = excess_space // 2
+ return Text.assemble(
+ (character * left, style),
+ text,
+ (character * (excess_space - left), style),
+ no_wrap=True,
+ end="",
+ )
+ else:
+ return Text.assemble(
+ (character * excess_space, style),
+ text,
+ no_wrap=True,
+ end="",
+ )
+ return text
+
+ title_text = self._title
+ if title_text is not None:
+ title_text.stylize_before(border_style)
+
+ child_width = (
+ width - 2
+ if self.expand
+ else console.measure(
+ renderable, options=options.update_width(width - 2)
+ ).maximum
+ )
+ child_height = self.height or options.height or None
+ if child_height:
+ child_height -= 2
+ if title_text is not None:
+ child_width = min(
+ options.max_width - 2, max(child_width, title_text.cell_len + 2)
+ )
+
+ width = child_width + 2
+ child_options = options.update(
+ width=child_width, height=child_height, highlight=self.highlight
+ )
+ lines = console.render_lines(renderable, child_options, style=style)
+
+ line_start = Segment(box.mid_left, border_style)
+ line_end = Segment(f"{box.mid_right}", border_style)
+ new_line = Segment.line()
+ if title_text is None or width <= 4:
+ yield Segment(box.get_top([width - 2]), border_style)
+ else:
+ title_text = align_text(
+ title_text,
+ width - 4,
+ self.title_align,
+ box.top,
+ border_style,
+ )
+ yield Segment(box.top_left + box.top, border_style)
+ yield from console.render(title_text, child_options.update_width(width - 4))
+ yield Segment(box.top + box.top_right, border_style)
+
+ yield new_line
+ for line in lines:
+ yield line_start
+ yield from line
+ yield line_end
+ yield new_line
+
+ subtitle_text = self._subtitle
+ if subtitle_text is not None:
+ subtitle_text.stylize_before(border_style)
+
+ if subtitle_text is None or width <= 4:
+ yield Segment(box.get_bottom([width - 2]), border_style)
+ else:
+ subtitle_text = align_text(
+ subtitle_text,
+ width - 4,
+ self.subtitle_align,
+ box.bottom,
+ border_style,
+ )
+ yield Segment(box.bottom_left + box.bottom, border_style)
+ yield from console.render(
+ subtitle_text, child_options.update_width(width - 4)
+ )
+ yield Segment(box.bottom + box.bottom_right, border_style)
+
+ yield new_line
+
+ def __rich_measure__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "Measurement":
+ _title = self._title
+ _, right, _, left = Padding.unpack(self.padding)
+ padding = left + right
+ renderables = [self.renderable, _title] if _title else [self.renderable]
+
+ if self.width is None:
+ width = (
+ measure_renderables(
+ console,
+ options.update_width(options.max_width - padding - 2),
+ renderables,
+ ).maximum
+ + padding
+ + 2
+ )
+ else:
+ width = self.width
+ return Measurement(width, width)
+
+
+if __name__ == "__main__": # pragma: no cover
+ from .console import Console
+
+ c = Console()
+
+ from .box import DOUBLE, ROUNDED
+ from .padding import Padding
+
+ p = Panel(
+ "Hello, World!",
+ title="rich.Panel",
+ style="white on blue",
+ box=DOUBLE,
+ padding=1,
+ )
+
+ c.print()
+ c.print(p)
diff --git a/src/pip/_vendor/rich/pretty.py b/src/pip/_vendor/rich/pretty.py
new file mode 100644
index 000000000..847b558c9
--- /dev/null
+++ b/src/pip/_vendor/rich/pretty.py
@@ -0,0 +1,1029 @@
+import builtins
+import collections
+import dataclasses
+import inspect
+import os
+import sys
+from array import array
+from collections import Counter, UserDict, UserList, defaultdict, deque
+from dataclasses import dataclass, fields, is_dataclass
+from inspect import isclass
+from itertools import islice
+from types import MappingProxyType
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ DefaultDict,
+ Dict,
+ Iterable,
+ List,
+ Optional,
+ Sequence,
+ Set,
+ Tuple,
+ Union,
+)
+
+from pip._vendor.rich.repr import RichReprResult
+
+try:
+ import attr as _attr_module
+
+ _has_attrs = True
+except ImportError: # pragma: no cover
+ _has_attrs = False
+
+from . import get_console
+from ._loop import loop_last
+from ._pick import pick_bool
+from .abc import RichRenderable
+from .cells import cell_len
+from .highlighter import ReprHighlighter
+from .jupyter import JupyterMixin, JupyterRenderable
+from .measure import Measurement
+from .text import Text
+
+if TYPE_CHECKING:
+ from .console import (
+ Console,
+ ConsoleOptions,
+ HighlighterType,
+ JustifyMethod,
+ OverflowMethod,
+ RenderResult,
+ )
+
+
+JUPYTER_CLASSES_TO_NOT_RENDER = {
+ # Matplotlib "Artists" manage their own rendering in a Jupyter notebook, and we should not try to render them too.
+ # "Typically, all [Matplotlib] visible elements in a figure are subclasses of Artist."
+ "matplotlib.artist.Artist",
+}
+
+
+def _is_attr_object(obj: Any) -> bool:
+ """Check if an object was created with attrs module."""
+ return _has_attrs and _attr_module.has(type(obj))
+
+
+def _get_attr_fields(obj: Any) -> Sequence["_attr_module.Attribute[Any]"]:
+ """Get fields for an attrs object."""
+ return _attr_module.fields(type(obj)) if _has_attrs else []
+
+
+def _is_dataclass_repr(obj: object) -> bool:
+ """Check if an instance of a dataclass contains the default repr.
+
+ Args:
+ obj (object): A dataclass instance.
+
+ Returns:
+ bool: True if the default repr is used, False if there is a custom repr.
+ """
+ # Digging in to a lot of internals here
+ # Catching all exceptions in case something is missing on a non CPython implementation
+ try:
+ return obj.__repr__.__code__.co_filename == dataclasses.__file__
+ except Exception: # pragma: no coverage
+ return False
+
+
+_dummy_namedtuple = collections.namedtuple("_dummy_namedtuple", [])
+
+
+def _has_default_namedtuple_repr(obj: object) -> bool:
+ """Check if an instance of namedtuple contains the default repr
+
+ Args:
+ obj (object): A namedtuple
+
+ Returns:
+ bool: True if the default repr is used, False if there's a custom repr.
+ """
+ obj_file = None
+ try:
+ obj_file = inspect.getfile(obj.__repr__)
+ except (OSError, TypeError):
+ # OSError handles case where object is defined in __main__ scope, e.g. REPL - no filename available.
+ # TypeError trapped defensively, in case of object without filename slips through.
+ pass
+ default_repr_file = inspect.getfile(_dummy_namedtuple.__repr__)
+ return obj_file == default_repr_file
+
+
+def _ipy_display_hook(
+ value: Any,
+ console: Optional["Console"] = None,
+ overflow: "OverflowMethod" = "ignore",
+ crop: bool = False,
+ indent_guides: bool = False,
+ max_length: Optional[int] = None,
+ max_string: Optional[int] = None,
+ max_depth: Optional[int] = None,
+ expand_all: bool = False,
+) -> None:
+ # needed here to prevent circular import:
+ from ._inspect import is_object_one_of_types
+ from .console import ConsoleRenderable
+
+ # always skip rich generated jupyter renderables or None values
+ if _safe_isinstance(value, JupyterRenderable) or value is None:
+ return
+
+ console = console or get_console()
+ if console.is_jupyter:
+ # Delegate rendering to IPython if the object (and IPython) supports it
+ # https://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display
+ ipython_repr_methods = [
+ "_repr_html_",
+ "_repr_markdown_",
+ "_repr_json_",
+ "_repr_latex_",
+ "_repr_jpeg_",
+ "_repr_png_",
+ "_repr_svg_",
+ "_repr_mimebundle_",
+ ]
+ for repr_method in ipython_repr_methods:
+ method = getattr(value, repr_method, None)
+ if inspect.ismethod(method):
+ # Calling the method ourselves isn't ideal. The interface for the `_repr_*_` methods
+ # specifies that if they return None, then they should not be rendered
+ # by the notebook.
+ try:
+ repr_result = method()
+ except Exception:
+ continue # If the method raises, treat it as if it doesn't exist, try any others
+ if repr_result is not None:
+ return # Delegate rendering to IPython
+
+ # When in a Jupyter notebook let's avoid the display of some specific classes,
+ # as they result in the rendering of useless and noisy lines such as `<Figure size 432x288 with 1 Axes>`.
+ # What does this do?
+ # --> if the class has "matplotlib.artist.Artist" in its hierarchy for example, we don't render it.
+ if is_object_one_of_types(value, JUPYTER_CLASSES_TO_NOT_RENDER):
+ return
+
+ # certain renderables should start on a new line
+ if _safe_isinstance(value, ConsoleRenderable):
+ console.line()
+
+ console.print(
+ value
+ if _safe_isinstance(value, RichRenderable)
+ else Pretty(
+ value,
+ overflow=overflow,
+ indent_guides=indent_guides,
+ max_length=max_length,
+ max_string=max_string,
+ max_depth=max_depth,
+ expand_all=expand_all,
+ margin=12,
+ ),
+ crop=crop,
+ new_line_start=True,
+ )
+
+
+def _safe_isinstance(
+ obj: object, class_or_tuple: Union[type, Tuple[type, ...]]
+) -> bool:
+ """isinstance can fail in rare cases, for example types with no __class__"""
+ try:
+ return isinstance(obj, class_or_tuple)
+ except Exception:
+ return False
+
+
+def install(
+ console: Optional["Console"] = None,
+ overflow: "OverflowMethod" = "ignore",
+ crop: bool = False,
+ indent_guides: bool = False,
+ max_length: Optional[int] = None,
+ max_string: Optional[int] = None,
+ max_depth: Optional[int] = None,
+ expand_all: bool = False,
+) -> None:
+ """Install automatic pretty printing in the Python REPL.
+
+ Args:
+ console (Console, optional): Console instance or ``None`` to use global console. Defaults to None.
+ overflow (Optional[OverflowMethod], optional): Overflow method. Defaults to "ignore".
+ crop (Optional[bool], optional): Enable cropping of long lines. Defaults to False.
+ indent_guides (bool, optional): Enable indentation guides. Defaults to False.
+ max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
+ Defaults to None.
+ max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
+ max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None.
+ expand_all (bool, optional): Expand all containers. Defaults to False.
+ max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
+ """
+ from pip._vendor.rich import get_console
+
+ console = console or get_console()
+ assert console is not None
+
+ def display_hook(value: Any) -> None:
+ """Replacement sys.displayhook which prettifies objects with Rich."""
+ if value is not None:
+ assert console is not None
+ builtins._ = None # type: ignore[attr-defined]
+ console.print(
+ value
+ if _safe_isinstance(value, RichRenderable)
+ else Pretty(
+ value,
+ overflow=overflow,
+ indent_guides=indent_guides,
+ max_length=max_length,
+ max_string=max_string,
+ max_depth=max_depth,
+ expand_all=expand_all,
+ ),
+ crop=crop,
+ )
+ builtins._ = value # type: ignore[attr-defined]
+
+ try: # pragma: no cover
+ ip = get_ipython() # type: ignore[name-defined]
+ from IPython.core.formatters import BaseFormatter
+
+ class RichFormatter(BaseFormatter): # type: ignore[misc]
+ pprint: bool = True
+
+ def __call__(self, value: Any) -> Any:
+ if self.pprint:
+ return _ipy_display_hook(
+ value,
+ console=get_console(),
+ overflow=overflow,
+ indent_guides=indent_guides,
+ max_length=max_length,
+ max_string=max_string,
+ max_depth=max_depth,
+ expand_all=expand_all,
+ )
+ else:
+ return repr(value)
+
+ # replace plain text formatter with rich formatter
+ rich_formatter = RichFormatter()
+ ip.display_formatter.formatters["text/plain"] = rich_formatter
+ except Exception:
+ sys.displayhook = display_hook
+
+
+class Pretty(JupyterMixin):
+ """A rich renderable that pretty prints an object.
+
+ Args:
+ _object (Any): An object to pretty print.
+ highlighter (HighlighterType, optional): Highlighter object to apply to result, or None for ReprHighlighter. Defaults to None.
+ indent_size (int, optional): Number of spaces in indent. Defaults to 4.
+ justify (JustifyMethod, optional): Justify method, or None for default. Defaults to None.
+ overflow (OverflowMethod, optional): Overflow method, or None for default. Defaults to None.
+ no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to False.
+ indent_guides (bool, optional): Enable indentation guides. Defaults to False.
+ max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
+ Defaults to None.
+ max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
+ max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None.
+ expand_all (bool, optional): Expand all containers. Defaults to False.
+ margin (int, optional): Subtrace a margin from width to force containers to expand earlier. Defaults to 0.
+ insert_line (bool, optional): Insert a new line if the output has multiple new lines. Defaults to False.
+ """
+
+ def __init__(
+ self,
+ _object: Any,
+ highlighter: Optional["HighlighterType"] = None,
+ *,
+ indent_size: int = 4,
+ justify: Optional["JustifyMethod"] = None,
+ overflow: Optional["OverflowMethod"] = None,
+ no_wrap: Optional[bool] = False,
+ indent_guides: bool = False,
+ max_length: Optional[int] = None,
+ max_string: Optional[int] = None,
+ max_depth: Optional[int] = None,
+ expand_all: bool = False,
+ margin: int = 0,
+ insert_line: bool = False,
+ ) -> None:
+ self._object = _object
+ self.highlighter = highlighter or ReprHighlighter()
+ self.indent_size = indent_size
+ self.justify: Optional["JustifyMethod"] = justify
+ self.overflow: Optional["OverflowMethod"] = overflow
+ self.no_wrap = no_wrap
+ self.indent_guides = indent_guides
+ self.max_length = max_length
+ self.max_string = max_string
+ self.max_depth = max_depth
+ self.expand_all = expand_all
+ self.margin = margin
+ self.insert_line = insert_line
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ pretty_str = pretty_repr(
+ self._object,
+ max_width=options.max_width - self.margin,
+ indent_size=self.indent_size,
+ max_length=self.max_length,
+ max_string=self.max_string,
+ max_depth=self.max_depth,
+ expand_all=self.expand_all,
+ )
+ pretty_text = Text.from_ansi(
+ pretty_str,
+ justify=self.justify or options.justify,
+ overflow=self.overflow or options.overflow,
+ no_wrap=pick_bool(self.no_wrap, options.no_wrap),
+ style="pretty",
+ )
+ pretty_text = (
+ self.highlighter(pretty_text)
+ if pretty_text
+ else Text(
+ f"{type(self._object)}.__repr__ returned empty string",
+ style="dim italic",
+ )
+ )
+ if self.indent_guides and not options.ascii_only:
+ pretty_text = pretty_text.with_indent_guides(
+ self.indent_size, style="repr.indent"
+ )
+ if self.insert_line and "\n" in pretty_text:
+ yield ""
+ yield pretty_text
+
+ def __rich_measure__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "Measurement":
+ pretty_str = pretty_repr(
+ self._object,
+ max_width=options.max_width,
+ indent_size=self.indent_size,
+ max_length=self.max_length,
+ max_string=self.max_string,
+ expand_all=self.expand_all,
+ )
+ text_width = (
+ max(cell_len(line) for line in pretty_str.splitlines()) if pretty_str else 0
+ )
+ return Measurement(text_width, text_width)
+
+
+def _get_braces_for_defaultdict(_object: DefaultDict[Any, Any]) -> Tuple[str, str, str]:
+ return (
+ f"defaultdict({_object.default_factory!r}, {{",
+ "})",
+ f"defaultdict({_object.default_factory!r}, {{}})",
+ )
+
+
+def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]:
+ return (f"array({_object.typecode!r}, [", "])", f"array({_object.typecode!r})")
+
+
+_BRACES: Dict[type, Callable[[Any], Tuple[str, str, str]]] = {
+ os._Environ: lambda _object: ("environ({", "})", "environ({})"),
+ array: _get_braces_for_array,
+ defaultdict: _get_braces_for_defaultdict,
+ Counter: lambda _object: ("Counter({", "})", "Counter()"),
+ deque: lambda _object: ("deque([", "])", "deque()"),
+ dict: lambda _object: ("{", "}", "{}"),
+ UserDict: lambda _object: ("{", "}", "{}"),
+ frozenset: lambda _object: ("frozenset({", "})", "frozenset()"),
+ list: lambda _object: ("[", "]", "[]"),
+ UserList: lambda _object: ("[", "]", "[]"),
+ set: lambda _object: ("{", "}", "set()"),
+ tuple: lambda _object: ("(", ")", "()"),
+ MappingProxyType: lambda _object: ("mappingproxy({", "})", "mappingproxy({})"),
+}
+_CONTAINERS = tuple(_BRACES.keys())
+_MAPPING_CONTAINERS = (dict, os._Environ, MappingProxyType, UserDict)
+
+
+def is_expandable(obj: Any) -> bool:
+ """Check if an object may be expanded by pretty print."""
+ return (
+ _safe_isinstance(obj, _CONTAINERS)
+ or (is_dataclass(obj))
+ or (hasattr(obj, "__rich_repr__"))
+ or _is_attr_object(obj)
+ ) and not isclass(obj)
+
+
+@dataclass
+class Node:
+ """A node in a repr tree. May be atomic or a container."""
+
+ key_repr: str = ""
+ value_repr: str = ""
+ open_brace: str = ""
+ close_brace: str = ""
+ empty: str = ""
+ last: bool = False
+ is_tuple: bool = False
+ is_namedtuple: bool = False
+ children: Optional[List["Node"]] = None
+ key_separator = ": "
+ separator: str = ", "
+
+ def iter_tokens(self) -> Iterable[str]:
+ """Generate tokens for this node."""
+ if self.key_repr:
+ yield self.key_repr
+ yield self.key_separator
+ if self.value_repr:
+ yield self.value_repr
+ elif self.children is not None:
+ if self.children:
+ yield self.open_brace
+ if self.is_tuple and not self.is_namedtuple and len(self.children) == 1:
+ yield from self.children[0].iter_tokens()
+ yield ","
+ else:
+ for child in self.children:
+ yield from child.iter_tokens()
+ if not child.last:
+ yield self.separator
+ yield self.close_brace
+ else:
+ yield self.empty
+
+ def check_length(self, start_length: int, max_length: int) -> bool:
+ """Check the length fits within a limit.
+
+ Args:
+ start_length (int): Starting length of the line (indent, prefix, suffix).
+ max_length (int): Maximum length.
+
+ Returns:
+ bool: True if the node can be rendered within max length, otherwise False.
+ """
+ total_length = start_length
+ for token in self.iter_tokens():
+ total_length += cell_len(token)
+ if total_length > max_length:
+ return False
+ return True
+
+ def __str__(self) -> str:
+ repr_text = "".join(self.iter_tokens())
+ return repr_text
+
+ def render(
+ self, max_width: int = 80, indent_size: int = 4, expand_all: bool = False
+ ) -> str:
+ """Render the node to a pretty repr.
+
+ Args:
+ max_width (int, optional): Maximum width of the repr. Defaults to 80.
+ indent_size (int, optional): Size of indents. Defaults to 4.
+ expand_all (bool, optional): Expand all levels. Defaults to False.
+
+ Returns:
+ str: A repr string of the original object.
+ """
+ lines = [_Line(node=self, is_root=True)]
+ line_no = 0
+ while line_no < len(lines):
+ line = lines[line_no]
+ if line.expandable and not line.expanded:
+ if expand_all or not line.check_length(max_width):
+ lines[line_no : line_no + 1] = line.expand(indent_size)
+ line_no += 1
+
+ repr_str = "\n".join(str(line) for line in lines)
+ return repr_str
+
+
+@dataclass
+class _Line:
+ """A line in repr output."""
+
+ parent: Optional["_Line"] = None
+ is_root: bool = False
+ node: Optional[Node] = None
+ text: str = ""
+ suffix: str = ""
+ whitespace: str = ""
+ expanded: bool = False
+ last: bool = False
+
+ @property
+ def expandable(self) -> bool:
+ """Check if the line may be expanded."""
+ return bool(self.node is not None and self.node.children)
+
+ def check_length(self, max_length: int) -> bool:
+ """Check this line fits within a given number of cells."""
+ start_length = (
+ len(self.whitespace) + cell_len(self.text) + cell_len(self.suffix)
+ )
+ assert self.node is not None
+ return self.node.check_length(start_length, max_length)
+
+ def expand(self, indent_size: int) -> Iterable["_Line"]:
+ """Expand this line by adding children on their own line."""
+ node = self.node
+ assert node is not None
+ whitespace = self.whitespace
+ assert node.children
+ if node.key_repr:
+ new_line = yield _Line(
+ text=f"{node.key_repr}{node.key_separator}{node.open_brace}",
+ whitespace=whitespace,
+ )
+ else:
+ new_line = yield _Line(text=node.open_brace, whitespace=whitespace)
+ child_whitespace = self.whitespace + " " * indent_size
+ tuple_of_one = node.is_tuple and len(node.children) == 1
+ for last, child in loop_last(node.children):
+ separator = "," if tuple_of_one else node.separator
+ line = _Line(
+ parent=new_line,
+ node=child,
+ whitespace=child_whitespace,
+ suffix=separator,
+ last=last and not tuple_of_one,
+ )
+ yield line
+
+ yield _Line(
+ text=node.close_brace,
+ whitespace=whitespace,
+ suffix=self.suffix,
+ last=self.last,
+ )
+
+ def __str__(self) -> str:
+ if self.last:
+ return f"{self.whitespace}{self.text}{self.node or ''}"
+ else:
+ return (
+ f"{self.whitespace}{self.text}{self.node or ''}{self.suffix.rstrip()}"
+ )
+
+
+def _is_namedtuple(obj: Any) -> bool:
+ """Checks if an object is most likely a namedtuple. It is possible
+ to craft an object that passes this check and isn't a namedtuple, but
+ there is only a minuscule chance of this happening unintentionally.
+
+ Args:
+ obj (Any): The object to test
+
+ Returns:
+ bool: True if the object is a namedtuple. False otherwise.
+ """
+ try:
+ fields = getattr(obj, "_fields", None)
+ except Exception:
+ # Being very defensive - if we cannot get the attr then its not a namedtuple
+ return False
+ return isinstance(obj, tuple) and isinstance(fields, tuple)
+
+
+def traverse(
+ _object: Any,
+ max_length: Optional[int] = None,
+ max_string: Optional[int] = None,
+ max_depth: Optional[int] = None,
+) -> Node:
+ """Traverse object and generate a tree.
+
+ Args:
+ _object (Any): Object to be traversed.
+ max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
+ Defaults to None.
+ max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
+ Defaults to None.
+ max_depth (int, optional): Maximum depth of data structures, or None for no maximum.
+ Defaults to None.
+
+ Returns:
+ Node: The root of a tree structure which can be used to render a pretty repr.
+ """
+
+ def to_repr(obj: Any) -> str:
+ """Get repr string for an object, but catch errors."""
+ if (
+ max_string is not None
+ and _safe_isinstance(obj, (bytes, str))
+ and len(obj) > max_string
+ ):
+ truncated = len(obj) - max_string
+ obj_repr = f"{obj[:max_string]!r}+{truncated}"
+ else:
+ try:
+ obj_repr = repr(obj)
+ except Exception as error:
+ obj_repr = f"<repr-error {str(error)!r}>"
+ return obj_repr
+
+ visited_ids: Set[int] = set()
+ push_visited = visited_ids.add
+ pop_visited = visited_ids.remove
+
+ def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node:
+ """Walk the object depth first."""
+
+ obj_id = id(obj)
+ if obj_id in visited_ids:
+ # Recursion detected
+ return Node(value_repr="...")
+
+ obj_type = type(obj)
+ py_version = (sys.version_info.major, sys.version_info.minor)
+ children: List[Node]
+ reached_max_depth = max_depth is not None and depth >= max_depth
+
+ def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
+ for arg in rich_args:
+ if _safe_isinstance(arg, tuple):
+ if len(arg) == 3:
+ key, child, default = arg
+ if default == child:
+ continue
+ yield key, child
+ elif len(arg) == 2:
+ key, child = arg
+ yield key, child
+ elif len(arg) == 1:
+ yield arg[0]
+ else:
+ yield arg
+
+ try:
+ fake_attributes = hasattr(
+ obj, "awehoi234_wdfjwljet234_234wdfoijsdfmmnxpi492"
+ )
+ except Exception:
+ fake_attributes = False
+
+ rich_repr_result: Optional[RichReprResult] = None
+ if not fake_attributes:
+ try:
+ if hasattr(obj, "__rich_repr__") and not isclass(obj):
+ rich_repr_result = obj.__rich_repr__()
+ except Exception:
+ pass
+
+ if rich_repr_result is not None:
+ push_visited(obj_id)
+ angular = getattr(obj.__rich_repr__, "angular", False)
+ args = list(iter_rich_args(rich_repr_result))
+ class_name = obj.__class__.__name__
+
+ if args:
+ children = []
+ append = children.append
+
+ if reached_max_depth:
+ if angular:
+ node = Node(value_repr=f"<{class_name}...>")
+ else:
+ node = Node(value_repr=f"{class_name}(...)")
+ else:
+ if angular:
+ node = Node(
+ open_brace=f"<{class_name} ",
+ close_brace=">",
+ children=children,
+ last=root,
+ separator=" ",
+ )
+ else:
+ node = Node(
+ open_brace=f"{class_name}(",
+ close_brace=")",
+ children=children,
+ last=root,
+ )
+ for last, arg in loop_last(args):
+ if _safe_isinstance(arg, tuple):
+ key, child = arg
+ child_node = _traverse(child, depth=depth + 1)
+ child_node.last = last
+ child_node.key_repr = key
+ child_node.key_separator = "="
+ append(child_node)
+ else:
+ child_node = _traverse(arg, depth=depth + 1)
+ child_node.last = last
+ append(child_node)
+ else:
+ node = Node(
+ value_repr=f"<{class_name}>" if angular else f"{class_name}()",
+ children=[],
+ last=root,
+ )
+ pop_visited(obj_id)
+ elif _is_attr_object(obj) and not fake_attributes:
+ push_visited(obj_id)
+ children = []
+ append = children.append
+
+ attr_fields = _get_attr_fields(obj)
+ if attr_fields:
+ if reached_max_depth:
+ node = Node(value_repr=f"{obj.__class__.__name__}(...)")
+ else:
+ node = Node(
+ open_brace=f"{obj.__class__.__name__}(",
+ close_brace=")",
+ children=children,
+ last=root,
+ )
+
+ def iter_attrs() -> Iterable[
+ Tuple[str, Any, Optional[Callable[[Any], str]]]
+ ]:
+ """Iterate over attr fields and values."""
+ for attr in attr_fields:
+ if attr.repr:
+ try:
+ value = getattr(obj, attr.name)
+ except Exception as error:
+ # Can happen, albeit rarely
+ yield (attr.name, error, None)
+ else:
+ yield (
+ attr.name,
+ value,
+ attr.repr if callable(attr.repr) else None,
+ )
+
+ for last, (name, value, repr_callable) in loop_last(iter_attrs()):
+ if repr_callable:
+ child_node = Node(value_repr=str(repr_callable(value)))
+ else:
+ child_node = _traverse(value, depth=depth + 1)
+ child_node.last = last
+ child_node.key_repr = name
+ child_node.key_separator = "="
+ append(child_node)
+ else:
+ node = Node(
+ value_repr=f"{obj.__class__.__name__}()", children=[], last=root
+ )
+ pop_visited(obj_id)
+ elif (
+ is_dataclass(obj)
+ and not _safe_isinstance(obj, type)
+ and not fake_attributes
+ and (_is_dataclass_repr(obj) or py_version == (3, 6))
+ ):
+ push_visited(obj_id)
+ children = []
+ append = children.append
+ if reached_max_depth:
+ node = Node(value_repr=f"{obj.__class__.__name__}(...)")
+ else:
+ node = Node(
+ open_brace=f"{obj.__class__.__name__}(",
+ close_brace=")",
+ children=children,
+ last=root,
+ )
+
+ for last, field in loop_last(
+ field for field in fields(obj) if field.repr
+ ):
+ child_node = _traverse(getattr(obj, field.name), depth=depth + 1)
+ child_node.key_repr = field.name
+ child_node.last = last
+ child_node.key_separator = "="
+ append(child_node)
+
+ pop_visited(obj_id)
+ elif _is_namedtuple(obj) and _has_default_namedtuple_repr(obj):
+ push_visited(obj_id)
+ class_name = obj.__class__.__name__
+ if reached_max_depth:
+ # If we've reached the max depth, we still show the class name, but not its contents
+ node = Node(
+ value_repr=f"{class_name}(...)",
+ )
+ else:
+ children = []
+ append = children.append
+ node = Node(
+ open_brace=f"{class_name}(",
+ close_brace=")",
+ children=children,
+ empty=f"{class_name}()",
+ )
+ for last, (key, value) in loop_last(obj._asdict().items()):
+ child_node = _traverse(value, depth=depth + 1)
+ child_node.key_repr = key
+ child_node.last = last
+ child_node.key_separator = "="
+ append(child_node)
+ pop_visited(obj_id)
+ elif _safe_isinstance(obj, _CONTAINERS):
+ for container_type in _CONTAINERS:
+ if _safe_isinstance(obj, container_type):
+ obj_type = container_type
+ break
+
+ push_visited(obj_id)
+
+ open_brace, close_brace, empty = _BRACES[obj_type](obj)
+
+ if reached_max_depth:
+ node = Node(value_repr=f"{open_brace}...{close_brace}")
+ elif obj_type.__repr__ != type(obj).__repr__:
+ node = Node(value_repr=to_repr(obj), last=root)
+ elif obj:
+ children = []
+ node = Node(
+ open_brace=open_brace,
+ close_brace=close_brace,
+ children=children,
+ last=root,
+ )
+ append = children.append
+ num_items = len(obj)
+ last_item_index = num_items - 1
+
+ if _safe_isinstance(obj, _MAPPING_CONTAINERS):
+ iter_items = iter(obj.items())
+ if max_length is not None:
+ iter_items = islice(iter_items, max_length)
+ for index, (key, child) in enumerate(iter_items):
+ child_node = _traverse(child, depth=depth + 1)
+ child_node.key_repr = to_repr(key)
+ child_node.last = index == last_item_index
+ append(child_node)
+ else:
+ iter_values = iter(obj)
+ if max_length is not None:
+ iter_values = islice(iter_values, max_length)
+ for index, child in enumerate(iter_values):
+ child_node = _traverse(child, depth=depth + 1)
+ child_node.last = index == last_item_index
+ append(child_node)
+ if max_length is not None and num_items > max_length:
+ append(Node(value_repr=f"... +{num_items - max_length}", last=True))
+ else:
+ node = Node(empty=empty, children=[], last=root)
+
+ pop_visited(obj_id)
+ else:
+ node = Node(value_repr=to_repr(obj), last=root)
+ node.is_tuple = _safe_isinstance(obj, tuple)
+ node.is_namedtuple = _is_namedtuple(obj)
+ return node
+
+ node = _traverse(_object, root=True)
+ return node
+
+
+def pretty_repr(
+ _object: Any,
+ *,
+ max_width: int = 80,
+ indent_size: int = 4,
+ max_length: Optional[int] = None,
+ max_string: Optional[int] = None,
+ max_depth: Optional[int] = None,
+ expand_all: bool = False,
+) -> str:
+ """Prettify repr string by expanding on to new lines to fit within a given width.
+
+ Args:
+ _object (Any): Object to repr.
+ max_width (int, optional): Desired maximum width of repr string. Defaults to 80.
+ indent_size (int, optional): Number of spaces to indent. Defaults to 4.
+ max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
+ Defaults to None.
+ max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
+ Defaults to None.
+ max_depth (int, optional): Maximum depth of nested data structure, or None for no depth.
+ Defaults to None.
+ expand_all (bool, optional): Expand all containers regardless of available width. Defaults to False.
+
+ Returns:
+ str: A possibly multi-line representation of the object.
+ """
+
+ if _safe_isinstance(_object, Node):
+ node = _object
+ else:
+ node = traverse(
+ _object, max_length=max_length, max_string=max_string, max_depth=max_depth
+ )
+ repr_str: str = node.render(
+ max_width=max_width, indent_size=indent_size, expand_all=expand_all
+ )
+ return repr_str
+
+
+def pprint(
+ _object: Any,
+ *,
+ console: Optional["Console"] = None,
+ indent_guides: bool = True,
+ max_length: Optional[int] = None,
+ max_string: Optional[int] = None,
+ max_depth: Optional[int] = None,
+ expand_all: bool = False,
+) -> None:
+ """A convenience function for pretty printing.
+
+ Args:
+ _object (Any): Object to pretty print.
+ console (Console, optional): Console instance, or None to use default. Defaults to None.
+ max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
+ Defaults to None.
+ max_string (int, optional): Maximum length of strings before truncating, or None to disable. Defaults to None.
+ max_depth (int, optional): Maximum depth for nested data structures, or None for unlimited depth. Defaults to None.
+ indent_guides (bool, optional): Enable indentation guides. Defaults to True.
+ expand_all (bool, optional): Expand all containers. Defaults to False.
+ """
+ _console = get_console() if console is None else console
+ _console.print(
+ Pretty(
+ _object,
+ max_length=max_length,
+ max_string=max_string,
+ max_depth=max_depth,
+ indent_guides=indent_guides,
+ expand_all=expand_all,
+ overflow="ignore",
+ ),
+ soft_wrap=True,
+ )
+
+
+if __name__ == "__main__": # pragma: no cover
+
+ class BrokenRepr:
+ def __repr__(self) -> str:
+ 1 / 0
+ return "this will fail"
+
+ from typing import NamedTuple
+
+ class StockKeepingUnit(NamedTuple):
+ name: str
+ description: str
+ price: float
+ category: str
+ reviews: List[str]
+
+ d = defaultdict(int)
+ d["foo"] = 5
+ data = {
+ "foo": [
+ 1,
+ "Hello World!",
+ 100.123,
+ 323.232,
+ 432324.0,
+ {5, 6, 7, (1, 2, 3, 4), 8},
+ ],
+ "bar": frozenset({1, 2, 3}),
+ "defaultdict": defaultdict(
+ list, {"crumble": ["apple", "rhubarb", "butter", "sugar", "flour"]}
+ ),
+ "counter": Counter(
+ [
+ "apple",
+ "orange",
+ "pear",
+ "kumquat",
+ "kumquat",
+ "durian" * 100,
+ ]
+ ),
+ "atomic": (False, True, None),
+ "namedtuple": StockKeepingUnit(
+ "Sparkling British Spring Water",
+ "Carbonated spring water",
+ 0.9,
+ "water",
+ ["its amazing!", "its terrible!"],
+ ),
+ "Broken": BrokenRepr(),
+ }
+ data["foo"].append(data) # type: ignore[attr-defined]
+
+ from pip._vendor.rich import print
+
+ # print(Pretty(data, indent_guides=True, max_string=20))
+
+ class Thing:
+ def __repr__(self) -> str:
+ return "Hello\x1b[38;5;239m World!"
+
+ print(Pretty(Thing()))
diff --git a/src/pip/_vendor/rich/progress.py b/src/pip/_vendor/rich/progress.py
new file mode 100644
index 000000000..e7d163c13
--- /dev/null
+++ b/src/pip/_vendor/rich/progress.py
@@ -0,0 +1,1707 @@
+import io
+import sys
+import typing
+import warnings
+from abc import ABC, abstractmethod
+from collections import deque
+from collections.abc import Sized
+from dataclasses import dataclass, field
+from datetime import timedelta
+from io import RawIOBase, UnsupportedOperation
+from math import ceil
+from mmap import mmap
+from os import PathLike, stat
+from threading import Event, RLock, Thread
+from types import TracebackType
+from typing import (
+ Any,
+ BinaryIO,
+ Callable,
+ ContextManager,
+ Deque,
+ Dict,
+ Generic,
+ Iterable,
+ List,
+ NamedTuple,
+ NewType,
+ Optional,
+ Sequence,
+ TextIO,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+)
+
+if sys.version_info >= (3, 8):
+ from typing import Literal
+else:
+ from pip._vendor.typing_extensions import Literal # pragma: no cover
+
+from . import filesize, get_console
+from .console import Console, Group, JustifyMethod, RenderableType
+from .highlighter import Highlighter
+from .jupyter import JupyterMixin
+from .live import Live
+from .progress_bar import ProgressBar
+from .spinner import Spinner
+from .style import StyleType
+from .table import Column, Table
+from .text import Text, TextType
+
+TaskID = NewType("TaskID", int)
+
+ProgressType = TypeVar("ProgressType")
+
+GetTimeCallable = Callable[[], float]
+
+
+_I = typing.TypeVar("_I", TextIO, BinaryIO)
+
+
+class _TrackThread(Thread):
+ """A thread to periodically update progress."""
+
+ def __init__(self, progress: "Progress", task_id: "TaskID", update_period: float):
+ self.progress = progress
+ self.task_id = task_id
+ self.update_period = update_period
+ self.done = Event()
+
+ self.completed = 0
+ super().__init__()
+
+ def run(self) -> None:
+ task_id = self.task_id
+ advance = self.progress.advance
+ update_period = self.update_period
+ last_completed = 0
+ wait = self.done.wait
+ while not wait(update_period):
+ completed = self.completed
+ if last_completed != completed:
+ advance(task_id, completed - last_completed)
+ last_completed = completed
+
+ self.progress.update(self.task_id, completed=self.completed, refresh=True)
+
+ def __enter__(self) -> "_TrackThread":
+ self.start()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ self.done.set()
+ self.join()
+
+
+def track(
+ sequence: Union[Sequence[ProgressType], Iterable[ProgressType]],
+ description: str = "Working...",
+ total: Optional[float] = None,
+ auto_refresh: bool = True,
+ console: Optional[Console] = None,
+ transient: bool = False,
+ get_time: Optional[Callable[[], float]] = None,
+ refresh_per_second: float = 10,
+ style: StyleType = "bar.back",
+ complete_style: StyleType = "bar.complete",
+ finished_style: StyleType = "bar.finished",
+ pulse_style: StyleType = "bar.pulse",
+ update_period: float = 0.1,
+ disable: bool = False,
+ show_speed: bool = True,
+) -> Iterable[ProgressType]:
+ """Track progress by iterating over a sequence.
+
+ Args:
+ sequence (Iterable[ProgressType]): A sequence (must support "len") you wish to iterate over.
+ description (str, optional): Description of task show next to progress bar. Defaults to "Working".
+ total: (float, optional): Total number of steps. Default is len(sequence).
+ auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True.
+ transient: (bool, optional): Clear the progress on exit. Defaults to False.
+ console (Console, optional): Console to write to. Default creates internal Console instance.
+ refresh_per_second (float): Number of times per second to refresh the progress information. Defaults to 10.
+ style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
+ complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
+ finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.finished".
+ pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
+ update_period (float, optional): Minimum time (in seconds) between calls to update(). Defaults to 0.1.
+ disable (bool, optional): Disable display of progress.
+ show_speed (bool, optional): Show speed if total isn't known. Defaults to True.
+ Returns:
+ Iterable[ProgressType]: An iterable of the values in the sequence.
+
+ """
+
+ columns: List["ProgressColumn"] = (
+ [TextColumn("[progress.description]{task.description}")] if description else []
+ )
+ columns.extend(
+ (
+ BarColumn(
+ style=style,
+ complete_style=complete_style,
+ finished_style=finished_style,
+ pulse_style=pulse_style,
+ ),
+ TaskProgressColumn(show_speed=show_speed),
+ TimeRemainingColumn(),
+ )
+ )
+ progress = Progress(
+ *columns,
+ auto_refresh=auto_refresh,
+ console=console,
+ transient=transient,
+ get_time=get_time,
+ refresh_per_second=refresh_per_second or 10,
+ disable=disable,
+ )
+
+ with progress:
+ yield from progress.track(
+ sequence, total=total, description=description, update_period=update_period
+ )
+
+
+class _Reader(RawIOBase, BinaryIO):
+ """A reader that tracks progress while it's being read from."""
+
+ def __init__(
+ self,
+ handle: BinaryIO,
+ progress: "Progress",
+ task: TaskID,
+ close_handle: bool = True,
+ ) -> None:
+ self.handle = handle
+ self.progress = progress
+ self.task = task
+ self.close_handle = close_handle
+ self._closed = False
+
+ def __enter__(self) -> "_Reader":
+ self.handle.__enter__()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ self.close()
+
+ def __iter__(self) -> BinaryIO:
+ return self
+
+ def __next__(self) -> bytes:
+ line = next(self.handle)
+ self.progress.advance(self.task, advance=len(line))
+ return line
+
+ @property
+ def closed(self) -> bool:
+ return self._closed
+
+ def fileno(self) -> int:
+ return self.handle.fileno()
+
+ def isatty(self) -> bool:
+ return self.handle.isatty()
+
+ @property
+ def mode(self) -> str:
+ return self.handle.mode
+
+ @property
+ def name(self) -> str:
+ return self.handle.name
+
+ def readable(self) -> bool:
+ return self.handle.readable()
+
+ def seekable(self) -> bool:
+ return self.handle.seekable()
+
+ def writable(self) -> bool:
+ return False
+
+ def read(self, size: int = -1) -> bytes:
+ block = self.handle.read(size)
+ self.progress.advance(self.task, advance=len(block))
+ return block
+
+ def readinto(self, b: Union[bytearray, memoryview, mmap]): # type: ignore[no-untyped-def, override]
+ n = self.handle.readinto(b) # type: ignore[attr-defined]
+ self.progress.advance(self.task, advance=n)
+ return n
+
+ def readline(self, size: int = -1) -> bytes: # type: ignore[override]
+ line = self.handle.readline(size)
+ self.progress.advance(self.task, advance=len(line))
+ return line
+
+ def readlines(self, hint: int = -1) -> List[bytes]:
+ lines = self.handle.readlines(hint)
+ self.progress.advance(self.task, advance=sum(map(len, lines)))
+ return lines
+
+ def close(self) -> None:
+ if self.close_handle:
+ self.handle.close()
+ self._closed = True
+
+ def seek(self, offset: int, whence: int = 0) -> int:
+ pos = self.handle.seek(offset, whence)
+ self.progress.update(self.task, completed=pos)
+ return pos
+
+ def tell(self) -> int:
+ return self.handle.tell()
+
+ def write(self, s: Any) -> int:
+ raise UnsupportedOperation("write")
+
+
+class _ReadContext(ContextManager[_I], Generic[_I]):
+ """A utility class to handle a context for both a reader and a progress."""
+
+ def __init__(self, progress: "Progress", reader: _I) -> None:
+ self.progress = progress
+ self.reader: _I = reader
+
+ def __enter__(self) -> _I:
+ self.progress.start()
+ return self.reader.__enter__()
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ self.progress.stop()
+ self.reader.__exit__(exc_type, exc_val, exc_tb)
+
+
+def wrap_file(
+ file: BinaryIO,
+ total: int,
+ *,
+ description: str = "Reading...",
+ auto_refresh: bool = True,
+ console: Optional[Console] = None,
+ transient: bool = False,
+ get_time: Optional[Callable[[], float]] = None,
+ refresh_per_second: float = 10,
+ style: StyleType = "bar.back",
+ complete_style: StyleType = "bar.complete",
+ finished_style: StyleType = "bar.finished",
+ pulse_style: StyleType = "bar.pulse",
+ disable: bool = False,
+) -> ContextManager[BinaryIO]:
+ """Read bytes from a file while tracking progress.
+
+ Args:
+ file (Union[str, PathLike[str], BinaryIO]): The path to the file to read, or a file-like object in binary mode.
+ total (int): Total number of bytes to read.
+ description (str, optional): Description of task show next to progress bar. Defaults to "Reading".
+ auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True.
+ transient: (bool, optional): Clear the progress on exit. Defaults to False.
+ console (Console, optional): Console to write to. Default creates internal Console instance.
+ refresh_per_second (float): Number of times per second to refresh the progress information. Defaults to 10.
+ style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
+ complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
+ finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.finished".
+ pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
+ disable (bool, optional): Disable display of progress.
+ Returns:
+ ContextManager[BinaryIO]: A context manager yielding a progress reader.
+
+ """
+
+ columns: List["ProgressColumn"] = (
+ [TextColumn("[progress.description]{task.description}")] if description else []
+ )
+ columns.extend(
+ (
+ BarColumn(
+ style=style,
+ complete_style=complete_style,
+ finished_style=finished_style,
+ pulse_style=pulse_style,
+ ),
+ DownloadColumn(),
+ TimeRemainingColumn(),
+ )
+ )
+ progress = Progress(
+ *columns,
+ auto_refresh=auto_refresh,
+ console=console,
+ transient=transient,
+ get_time=get_time,
+ refresh_per_second=refresh_per_second or 10,
+ disable=disable,
+ )
+
+ reader = progress.wrap_file(file, total=total, description=description)
+ return _ReadContext(progress, reader)
+
+
+@typing.overload
+def open(
+ file: Union[str, "PathLike[str]", bytes],
+ mode: Union[Literal["rt"], Literal["r"]],
+ buffering: int = -1,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ newline: Optional[str] = None,
+ *,
+ total: Optional[int] = None,
+ description: str = "Reading...",
+ auto_refresh: bool = True,
+ console: Optional[Console] = None,
+ transient: bool = False,
+ get_time: Optional[Callable[[], float]] = None,
+ refresh_per_second: float = 10,
+ style: StyleType = "bar.back",
+ complete_style: StyleType = "bar.complete",
+ finished_style: StyleType = "bar.finished",
+ pulse_style: StyleType = "bar.pulse",
+ disable: bool = False,
+) -> ContextManager[TextIO]:
+ pass
+
+
+@typing.overload
+def open(
+ file: Union[str, "PathLike[str]", bytes],
+ mode: Literal["rb"],
+ buffering: int = -1,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ newline: Optional[str] = None,
+ *,
+ total: Optional[int] = None,
+ description: str = "Reading...",
+ auto_refresh: bool = True,
+ console: Optional[Console] = None,
+ transient: bool = False,
+ get_time: Optional[Callable[[], float]] = None,
+ refresh_per_second: float = 10,
+ style: StyleType = "bar.back",
+ complete_style: StyleType = "bar.complete",
+ finished_style: StyleType = "bar.finished",
+ pulse_style: StyleType = "bar.pulse",
+ disable: bool = False,
+) -> ContextManager[BinaryIO]:
+ pass
+
+
+def open(
+ file: Union[str, "PathLike[str]", bytes],
+ mode: Union[Literal["rb"], Literal["rt"], Literal["r"]] = "r",
+ buffering: int = -1,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ newline: Optional[str] = None,
+ *,
+ total: Optional[int] = None,
+ description: str = "Reading...",
+ auto_refresh: bool = True,
+ console: Optional[Console] = None,
+ transient: bool = False,
+ get_time: Optional[Callable[[], float]] = None,
+ refresh_per_second: float = 10,
+ style: StyleType = "bar.back",
+ complete_style: StyleType = "bar.complete",
+ finished_style: StyleType = "bar.finished",
+ pulse_style: StyleType = "bar.pulse",
+ disable: bool = False,
+) -> Union[ContextManager[BinaryIO], ContextManager[TextIO]]:
+ """Read bytes from a file while tracking progress.
+
+ Args:
+ path (Union[str, PathLike[str], BinaryIO]): The path to the file to read, or a file-like object in binary mode.
+ mode (str): The mode to use to open the file. Only supports "r", "rb" or "rt".
+ buffering (int): The buffering strategy to use, see :func:`io.open`.
+ encoding (str, optional): The encoding to use when reading in text mode, see :func:`io.open`.
+ errors (str, optional): The error handling strategy for decoding errors, see :func:`io.open`.
+ newline (str, optional): The strategy for handling newlines in text mode, see :func:`io.open`
+ total: (int, optional): Total number of bytes to read. Must be provided if reading from a file handle. Default for a path is os.stat(file).st_size.
+ description (str, optional): Description of task show next to progress bar. Defaults to "Reading".
+ auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True.
+ transient: (bool, optional): Clear the progress on exit. Defaults to False.
+ console (Console, optional): Console to write to. Default creates internal Console instance.
+ refresh_per_second (float): Number of times per second to refresh the progress information. Defaults to 10.
+ style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
+ complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
+ finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.finished".
+ pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
+ disable (bool, optional): Disable display of progress.
+ encoding (str, optional): The encoding to use when reading in text mode.
+
+ Returns:
+ ContextManager[BinaryIO]: A context manager yielding a progress reader.
+
+ """
+
+ columns: List["ProgressColumn"] = (
+ [TextColumn("[progress.description]{task.description}")] if description else []
+ )
+ columns.extend(
+ (
+ BarColumn(
+ style=style,
+ complete_style=complete_style,
+ finished_style=finished_style,
+ pulse_style=pulse_style,
+ ),
+ DownloadColumn(),
+ TimeRemainingColumn(),
+ )
+ )
+ progress = Progress(
+ *columns,
+ auto_refresh=auto_refresh,
+ console=console,
+ transient=transient,
+ get_time=get_time,
+ refresh_per_second=refresh_per_second or 10,
+ disable=disable,
+ )
+
+ reader = progress.open(
+ file,
+ mode=mode,
+ buffering=buffering,
+ encoding=encoding,
+ errors=errors,
+ newline=newline,
+ total=total,
+ description=description,
+ )
+ return _ReadContext(progress, reader) # type: ignore[return-value, type-var]
+
+
+class ProgressColumn(ABC):
+ """Base class for a widget to use in progress display."""
+
+ max_refresh: Optional[float] = None
+
+ def __init__(self, table_column: Optional[Column] = None) -> None:
+ self._table_column = table_column
+ self._renderable_cache: Dict[TaskID, Tuple[float, RenderableType]] = {}
+ self._update_time: Optional[float] = None
+
+ def get_table_column(self) -> Column:
+ """Get a table column, used to build tasks table."""
+ return self._table_column or Column()
+
+ def __call__(self, task: "Task") -> RenderableType:
+ """Called by the Progress object to return a renderable for the given task.
+
+ Args:
+ task (Task): An object containing information regarding the task.
+
+ Returns:
+ RenderableType: Anything renderable (including str).
+ """
+ current_time = task.get_time()
+ if self.max_refresh is not None and not task.completed:
+ try:
+ timestamp, renderable = self._renderable_cache[task.id]
+ except KeyError:
+ pass
+ else:
+ if timestamp + self.max_refresh > current_time:
+ return renderable
+
+ renderable = self.render(task)
+ self._renderable_cache[task.id] = (current_time, renderable)
+ return renderable
+
+ @abstractmethod
+ def render(self, task: "Task") -> RenderableType:
+ """Should return a renderable object."""
+
+
+class RenderableColumn(ProgressColumn):
+ """A column to insert an arbitrary column.
+
+ Args:
+ renderable (RenderableType, optional): Any renderable. Defaults to empty string.
+ """
+
+ def __init__(
+ self, renderable: RenderableType = "", *, table_column: Optional[Column] = None
+ ):
+ self.renderable = renderable
+ super().__init__(table_column=table_column)
+
+ def render(self, task: "Task") -> RenderableType:
+ return self.renderable
+
+
+class SpinnerColumn(ProgressColumn):
+ """A column with a 'spinner' animation.
+
+ Args:
+ spinner_name (str, optional): Name of spinner animation. Defaults to "dots".
+ style (StyleType, optional): Style of spinner. Defaults to "progress.spinner".
+ speed (float, optional): Speed factor of spinner. Defaults to 1.0.
+ finished_text (TextType, optional): Text used when task is finished. Defaults to " ".
+ """
+
+ def __init__(
+ self,
+ spinner_name: str = "dots",
+ style: Optional[StyleType] = "progress.spinner",
+ speed: float = 1.0,
+ finished_text: TextType = " ",
+ table_column: Optional[Column] = None,
+ ):
+ self.spinner = Spinner(spinner_name, style=style, speed=speed)
+ self.finished_text = (
+ Text.from_markup(finished_text)
+ if isinstance(finished_text, str)
+ else finished_text
+ )
+ super().__init__(table_column=table_column)
+
+ def set_spinner(
+ self,
+ spinner_name: str,
+ spinner_style: Optional[StyleType] = "progress.spinner",
+ speed: float = 1.0,
+ ) -> None:
+ """Set a new spinner.
+
+ Args:
+ spinner_name (str): Spinner name, see python -m rich.spinner.
+ spinner_style (Optional[StyleType], optional): Spinner style. Defaults to "progress.spinner".
+ speed (float, optional): Speed factor of spinner. Defaults to 1.0.
+ """
+ self.spinner = Spinner(spinner_name, style=spinner_style, speed=speed)
+
+ def render(self, task: "Task") -> RenderableType:
+ text = (
+ self.finished_text
+ if task.finished
+ else self.spinner.render(task.get_time())
+ )
+ return text
+
+
+class TextColumn(ProgressColumn):
+ """A column containing text."""
+
+ def __init__(
+ self,
+ text_format: str,
+ style: StyleType = "none",
+ justify: JustifyMethod = "left",
+ markup: bool = True,
+ highlighter: Optional[Highlighter] = None,
+ table_column: Optional[Column] = None,
+ ) -> None:
+ self.text_format = text_format
+ self.justify: JustifyMethod = justify
+ self.style = style
+ self.markup = markup
+ self.highlighter = highlighter
+ super().__init__(table_column=table_column or Column(no_wrap=True))
+
+ def render(self, task: "Task") -> Text:
+ _text = self.text_format.format(task=task)
+ if self.markup:
+ text = Text.from_markup(_text, style=self.style, justify=self.justify)
+ else:
+ text = Text(_text, style=self.style, justify=self.justify)
+ if self.highlighter:
+ self.highlighter.highlight(text)
+ return text
+
+
+class BarColumn(ProgressColumn):
+ """Renders a visual progress bar.
+
+ Args:
+ bar_width (Optional[int], optional): Width of bar or None for full width. Defaults to 40.
+ style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
+ complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
+ finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.finished".
+ pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
+ """
+
+ def __init__(
+ self,
+ bar_width: Optional[int] = 40,
+ style: StyleType = "bar.back",
+ complete_style: StyleType = "bar.complete",
+ finished_style: StyleType = "bar.finished",
+ pulse_style: StyleType = "bar.pulse",
+ table_column: Optional[Column] = None,
+ ) -> None:
+ self.bar_width = bar_width
+ self.style = style
+ self.complete_style = complete_style
+ self.finished_style = finished_style
+ self.pulse_style = pulse_style
+ super().__init__(table_column=table_column)
+
+ def render(self, task: "Task") -> ProgressBar:
+ """Gets a progress bar widget for a task."""
+ return ProgressBar(
+ total=max(0, task.total) if task.total is not None else None,
+ completed=max(0, task.completed),
+ width=None if self.bar_width is None else max(1, self.bar_width),
+ pulse=not task.started,
+ animation_time=task.get_time(),
+ style=self.style,
+ complete_style=self.complete_style,
+ finished_style=self.finished_style,
+ pulse_style=self.pulse_style,
+ )
+
+
+class TimeElapsedColumn(ProgressColumn):
+ """Renders time elapsed."""
+
+ def render(self, task: "Task") -> Text:
+ """Show time remaining."""
+ elapsed = task.finished_time if task.finished else task.elapsed
+ if elapsed is None:
+ return Text("-:--:--", style="progress.elapsed")
+ delta = timedelta(seconds=int(elapsed))
+ return Text(str(delta), style="progress.elapsed")
+
+
+class TaskProgressColumn(TextColumn):
+ """Show task progress as a percentage.
+
+ Args:
+ text_format (str, optional): Format for percentage display. Defaults to "[progress.percentage]{task.percentage:>3.0f}%".
+ text_format_no_percentage (str, optional): Format if percentage is unknown. Defaults to "".
+ style (StyleType, optional): Style of output. Defaults to "none".
+ justify (JustifyMethod, optional): Text justification. Defaults to "left".
+ markup (bool, optional): Enable markup. Defaults to True.
+ highlighter (Optional[Highlighter], optional): Highlighter to apply to output. Defaults to None.
+ table_column (Optional[Column], optional): Table Column to use. Defaults to None.
+ show_speed (bool, optional): Show speed if total is unknown. Defaults to False.
+ """
+
+ def __init__(
+ self,
+ text_format: str = "[progress.percentage]{task.percentage:>3.0f}%",
+ text_format_no_percentage: str = "",
+ style: StyleType = "none",
+ justify: JustifyMethod = "left",
+ markup: bool = True,
+ highlighter: Optional[Highlighter] = None,
+ table_column: Optional[Column] = None,
+ show_speed: bool = False,
+ ) -> None:
+
+ self.text_format_no_percentage = text_format_no_percentage
+ self.show_speed = show_speed
+ super().__init__(
+ text_format=text_format,
+ style=style,
+ justify=justify,
+ markup=markup,
+ highlighter=highlighter,
+ table_column=table_column,
+ )
+
+ @classmethod
+ def render_speed(cls, speed: Optional[float]) -> Text:
+ """Render the speed in iterations per second.
+
+ Args:
+ task (Task): A Task object.
+
+ Returns:
+ Text: Text object containing the task speed.
+ """
+ if speed is None:
+ return Text("", style="progress.percentage")
+ unit, suffix = filesize.pick_unit_and_suffix(
+ int(speed),
+ ["", "×10³", "×10⁶", "×10⁹", "×10¹²"],
+ 1000,
+ )
+ data_speed = speed / unit
+ return Text(f"{data_speed:.1f}{suffix} it/s", style="progress.percentage")
+
+ def render(self, task: "Task") -> Text:
+ if task.total is None and self.show_speed:
+ return self.render_speed(task.finished_speed or task.speed)
+ text_format = (
+ self.text_format_no_percentage if task.total is None else self.text_format
+ )
+ _text = text_format.format(task=task)
+ if self.markup:
+ text = Text.from_markup(_text, style=self.style, justify=self.justify)
+ else:
+ text = Text(_text, style=self.style, justify=self.justify)
+ if self.highlighter:
+ self.highlighter.highlight(text)
+ return text
+
+
+class TimeRemainingColumn(ProgressColumn):
+ """Renders estimated time remaining.
+
+ Args:
+ compact (bool, optional): Render MM:SS when time remaining is less than an hour. Defaults to False.
+ elapsed_when_finished (bool, optional): Render time elapsed when the task is finished. Defaults to False.
+ """
+
+ # Only refresh twice a second to prevent jitter
+ max_refresh = 0.5
+
+ def __init__(
+ self,
+ compact: bool = False,
+ elapsed_when_finished: bool = False,
+ table_column: Optional[Column] = None,
+ ):
+ self.compact = compact
+ self.elapsed_when_finished = elapsed_when_finished
+ super().__init__(table_column=table_column)
+
+ def render(self, task: "Task") -> Text:
+ """Show time remaining."""
+ if self.elapsed_when_finished and task.finished:
+ task_time = task.finished_time
+ style = "progress.elapsed"
+ else:
+ task_time = task.time_remaining
+ style = "progress.remaining"
+
+ if task.total is None:
+ return Text("", style=style)
+
+ if task_time is None:
+ return Text("--:--" if self.compact else "-:--:--", style=style)
+
+ # Based on https://github.com/tqdm/tqdm/blob/master/tqdm/std.py
+ minutes, seconds = divmod(int(task_time), 60)
+ hours, minutes = divmod(minutes, 60)
+
+ if self.compact and not hours:
+ formatted = f"{minutes:02d}:{seconds:02d}"
+ else:
+ formatted = f"{hours:d}:{minutes:02d}:{seconds:02d}"
+
+ return Text(formatted, style=style)
+
+
+class FileSizeColumn(ProgressColumn):
+ """Renders completed filesize."""
+
+ def render(self, task: "Task") -> Text:
+ """Show data completed."""
+ data_size = filesize.decimal(int(task.completed))
+ return Text(data_size, style="progress.filesize")
+
+
+class TotalFileSizeColumn(ProgressColumn):
+ """Renders total filesize."""
+
+ def render(self, task: "Task") -> Text:
+ """Show data completed."""
+ data_size = filesize.decimal(int(task.total)) if task.total is not None else ""
+ return Text(data_size, style="progress.filesize.total")
+
+
+class MofNCompleteColumn(ProgressColumn):
+ """Renders completed count/total, e.g. ' 10/1000'.
+
+ Best for bounded tasks with int quantities.
+
+ Space pads the completed count so that progress length does not change as task progresses
+ past powers of 10.
+
+ Args:
+ separator (str, optional): Text to separate completed and total values. Defaults to "/".
+ """
+
+ def __init__(self, separator: str = "/", table_column: Optional[Column] = None):
+ self.separator = separator
+ super().__init__(table_column=table_column)
+
+ def render(self, task: "Task") -> Text:
+ """Show completed/total."""
+ completed = int(task.completed)
+ total = int(task.total) if task.total is not None else "?"
+ total_width = len(str(total))
+ return Text(
+ f"{completed:{total_width}d}{self.separator}{total}",
+ style="progress.download",
+ )
+
+
+class DownloadColumn(ProgressColumn):
+ """Renders file size downloaded and total, e.g. '0.5/2.3 GB'.
+
+ Args:
+ binary_units (bool, optional): Use binary units, KiB, MiB etc. Defaults to False.
+ """
+
+ def __init__(
+ self, binary_units: bool = False, table_column: Optional[Column] = None
+ ) -> None:
+ self.binary_units = binary_units
+ super().__init__(table_column=table_column)
+
+ def render(self, task: "Task") -> Text:
+ """Calculate common unit for completed and total."""
+ completed = int(task.completed)
+
+ unit_and_suffix_calculation_base = (
+ int(task.total) if task.total is not None else completed
+ )
+ if self.binary_units:
+ unit, suffix = filesize.pick_unit_and_suffix(
+ unit_and_suffix_calculation_base,
+ ["bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"],
+ 1024,
+ )
+ else:
+ unit, suffix = filesize.pick_unit_and_suffix(
+ unit_and_suffix_calculation_base,
+ ["bytes", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
+ 1000,
+ )
+ precision = 0 if unit == 1 else 1
+
+ completed_ratio = completed / unit
+ completed_str = f"{completed_ratio:,.{precision}f}"
+
+ if task.total is not None:
+ total = int(task.total)
+ total_ratio = total / unit
+ total_str = f"{total_ratio:,.{precision}f}"
+ else:
+ total_str = "?"
+
+ download_status = f"{completed_str}/{total_str} {suffix}"
+ download_text = Text(download_status, style="progress.download")
+ return download_text
+
+
+class TransferSpeedColumn(ProgressColumn):
+ """Renders human readable transfer speed."""
+
+ def render(self, task: "Task") -> Text:
+ """Show data transfer speed."""
+ speed = task.finished_speed or task.speed
+ if speed is None:
+ return Text("?", style="progress.data.speed")
+ data_speed = filesize.decimal(int(speed))
+ return Text(f"{data_speed}/s", style="progress.data.speed")
+
+
+class ProgressSample(NamedTuple):
+ """Sample of progress for a given time."""
+
+ timestamp: float
+ """Timestamp of sample."""
+ completed: float
+ """Number of steps completed."""
+
+
+@dataclass
+class Task:
+ """Information regarding a progress task.
+
+ This object should be considered read-only outside of the :class:`~Progress` class.
+
+ """
+
+ id: TaskID
+ """Task ID associated with this task (used in Progress methods)."""
+
+ description: str
+ """str: Description of the task."""
+
+ total: Optional[float]
+ """Optional[float]: Total number of steps in this task."""
+
+ completed: float
+ """float: Number of steps completed"""
+
+ _get_time: GetTimeCallable
+ """Callable to get the current time."""
+
+ finished_time: Optional[float] = None
+ """float: Time task was finished."""
+
+ visible: bool = True
+ """bool: Indicates if this task is visible in the progress display."""
+
+ fields: Dict[str, Any] = field(default_factory=dict)
+ """dict: Arbitrary fields passed in via Progress.update."""
+
+ start_time: Optional[float] = field(default=None, init=False, repr=False)
+ """Optional[float]: Time this task was started, or None if not started."""
+
+ stop_time: Optional[float] = field(default=None, init=False, repr=False)
+ """Optional[float]: Time this task was stopped, or None if not stopped."""
+
+ finished_speed: Optional[float] = None
+ """Optional[float]: The last speed for a finished task."""
+
+ _progress: Deque[ProgressSample] = field(
+ default_factory=lambda: deque(maxlen=1000), init=False, repr=False
+ )
+
+ _lock: RLock = field(repr=False, default_factory=RLock)
+ """Thread lock."""
+
+ def get_time(self) -> float:
+ """float: Get the current time, in seconds."""
+ return self._get_time()
+
+ @property
+ def started(self) -> bool:
+ """bool: Check if the task as started."""
+ return self.start_time is not None
+
+ @property
+ def remaining(self) -> Optional[float]:
+ """Optional[float]: Get the number of steps remaining, if a non-None total was set."""
+ if self.total is None:
+ return None
+ return self.total - self.completed
+
+ @property
+ def elapsed(self) -> Optional[float]:
+ """Optional[float]: Time elapsed since task was started, or ``None`` if the task hasn't started."""
+ if self.start_time is None:
+ return None
+ if self.stop_time is not None:
+ return self.stop_time - self.start_time
+ return self.get_time() - self.start_time
+
+ @property
+ def finished(self) -> bool:
+ """Check if the task has finished."""
+ return self.finished_time is not None
+
+ @property
+ def percentage(self) -> float:
+ """float: Get progress of task as a percentage. If a None total was set, returns 0"""
+ if not self.total:
+ return 0.0
+ completed = (self.completed / self.total) * 100.0
+ completed = min(100.0, max(0.0, completed))
+ return completed
+
+ @property
+ def speed(self) -> Optional[float]:
+ """Optional[float]: Get the estimated speed in steps per second."""
+ if self.start_time is None:
+ return None
+ with self._lock:
+ progress = self._progress
+ if not progress:
+ return None
+ total_time = progress[-1].timestamp - progress[0].timestamp
+ if total_time == 0:
+ return None
+ iter_progress = iter(progress)
+ next(iter_progress)
+ total_completed = sum(sample.completed for sample in iter_progress)
+ speed = total_completed / total_time
+ return speed
+
+ @property
+ def time_remaining(self) -> Optional[float]:
+ """Optional[float]: Get estimated time to completion, or ``None`` if no data."""
+ if self.finished:
+ return 0.0
+ speed = self.speed
+ if not speed:
+ return None
+ remaining = self.remaining
+ if remaining is None:
+ return None
+ estimate = ceil(remaining / speed)
+ return estimate
+
+ def _reset(self) -> None:
+ """Reset progress."""
+ self._progress.clear()
+ self.finished_time = None
+ self.finished_speed = None
+
+
+class Progress(JupyterMixin):
+ """Renders an auto-updating progress bar(s).
+
+ Args:
+ console (Console, optional): Optional Console instance. Default will an internal Console instance writing to stdout.
+ auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()`.
+ refresh_per_second (Optional[float], optional): Number of times per second to refresh the progress information or None to use default (10). Defaults to None.
+ speed_estimate_period: (float, optional): Period (in seconds) used to calculate the speed estimate. Defaults to 30.
+ transient: (bool, optional): Clear the progress on exit. Defaults to False.
+ redirect_stdout: (bool, optional): Enable redirection of stdout, so ``print`` may be used. Defaults to True.
+ redirect_stderr: (bool, optional): Enable redirection of stderr. Defaults to True.
+ get_time: (Callable, optional): A callable that gets the current time, or None to use Console.get_time. Defaults to None.
+ disable (bool, optional): Disable progress display. Defaults to False
+ expand (bool, optional): Expand tasks table to fit width. Defaults to False.
+ """
+
+ def __init__(
+ self,
+ *columns: Union[str, ProgressColumn],
+ console: Optional[Console] = None,
+ auto_refresh: bool = True,
+ refresh_per_second: float = 10,
+ speed_estimate_period: float = 30.0,
+ transient: bool = False,
+ redirect_stdout: bool = True,
+ redirect_stderr: bool = True,
+ get_time: Optional[GetTimeCallable] = None,
+ disable: bool = False,
+ expand: bool = False,
+ ) -> None:
+ assert refresh_per_second > 0, "refresh_per_second must be > 0"
+ self._lock = RLock()
+ self.columns = columns or self.get_default_columns()
+ self.speed_estimate_period = speed_estimate_period
+
+ self.disable = disable
+ self.expand = expand
+ self._tasks: Dict[TaskID, Task] = {}
+ self._task_index: TaskID = TaskID(0)
+ self.live = Live(
+ console=console or get_console(),
+ auto_refresh=auto_refresh,
+ refresh_per_second=refresh_per_second,
+ transient=transient,
+ redirect_stdout=redirect_stdout,
+ redirect_stderr=redirect_stderr,
+ get_renderable=self.get_renderable,
+ )
+ self.get_time = get_time or self.console.get_time
+ self.print = self.console.print
+ self.log = self.console.log
+
+ @classmethod
+ def get_default_columns(cls) -> Tuple[ProgressColumn, ...]:
+ """Get the default columns used for a new Progress instance:
+ - a text column for the description (TextColumn)
+ - the bar itself (BarColumn)
+ - a text column showing completion percentage (TextColumn)
+ - an estimated-time-remaining column (TimeRemainingColumn)
+ If the Progress instance is created without passing a columns argument,
+ the default columns defined here will be used.
+
+ You can also create a Progress instance using custom columns before
+ and/or after the defaults, as in this example:
+
+ progress = Progress(
+ SpinnerColumn(),
+ *Progress.default_columns(),
+ "Elapsed:",
+ TimeElapsedColumn(),
+ )
+
+ This code shows the creation of a Progress display, containing
+ a spinner to the left, the default columns, and a labeled elapsed
+ time column.
+ """
+ return (
+ TextColumn("[progress.description]{task.description}"),
+ BarColumn(),
+ TaskProgressColumn(),
+ TimeRemainingColumn(),
+ )
+
+ @property
+ def console(self) -> Console:
+ return self.live.console
+
+ @property
+ def tasks(self) -> List[Task]:
+ """Get a list of Task instances."""
+ with self._lock:
+ return list(self._tasks.values())
+
+ @property
+ def task_ids(self) -> List[TaskID]:
+ """A list of task IDs."""
+ with self._lock:
+ return list(self._tasks.keys())
+
+ @property
+ def finished(self) -> bool:
+ """Check if all tasks have been completed."""
+ with self._lock:
+ if not self._tasks:
+ return True
+ return all(task.finished for task in self._tasks.values())
+
+ def start(self) -> None:
+ """Start the progress display."""
+ if not self.disable:
+ self.live.start(refresh=True)
+
+ def stop(self) -> None:
+ """Stop the progress display."""
+ self.live.stop()
+ if not self.console.is_interactive:
+ self.console.print()
+
+ def __enter__(self) -> "Progress":
+ self.start()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ self.stop()
+
+ def track(
+ self,
+ sequence: Union[Iterable[ProgressType], Sequence[ProgressType]],
+ total: Optional[float] = None,
+ task_id: Optional[TaskID] = None,
+ description: str = "Working...",
+ update_period: float = 0.1,
+ ) -> Iterable[ProgressType]:
+ """Track progress by iterating over a sequence.
+
+ Args:
+ sequence (Sequence[ProgressType]): A sequence of values you want to iterate over and track progress.
+ total: (float, optional): Total number of steps. Default is len(sequence).
+ task_id: (TaskID): Task to track. Default is new task.
+ description: (str, optional): Description of task, if new task is created.
+ update_period (float, optional): Minimum time (in seconds) between calls to update(). Defaults to 0.1.
+
+ Returns:
+ Iterable[ProgressType]: An iterable of values taken from the provided sequence.
+ """
+
+ task_total: Optional[float] = None
+ if total is None:
+ if isinstance(sequence, Sized):
+ task_total = float(len(sequence))
+ else:
+ task_total = total
+
+ if task_id is None:
+ task_id = self.add_task(description, total=task_total)
+ else:
+ self.update(task_id, total=task_total)
+
+ if self.live.auto_refresh:
+ with _TrackThread(self, task_id, update_period) as track_thread:
+ for value in sequence:
+ yield value
+ track_thread.completed += 1
+ else:
+ advance = self.advance
+ refresh = self.refresh
+ for value in sequence:
+ yield value
+ advance(task_id, 1)
+ refresh()
+
+ def wrap_file(
+ self,
+ file: BinaryIO,
+ total: Optional[int] = None,
+ *,
+ task_id: Optional[TaskID] = None,
+ description: str = "Reading...",
+ ) -> BinaryIO:
+ """Track progress file reading from a binary file.
+
+ Args:
+ file (BinaryIO): A file-like object opened in binary mode.
+ total (int, optional): Total number of bytes to read. This must be provided unless a task with a total is also given.
+ task_id (TaskID): Task to track. Default is new task.
+ description (str, optional): Description of task, if new task is created.
+
+ Returns:
+ BinaryIO: A readable file-like object in binary mode.
+
+ Raises:
+ ValueError: When no total value can be extracted from the arguments or the task.
+ """
+ # attempt to recover the total from the task
+ total_bytes: Optional[float] = None
+ if total is not None:
+ total_bytes = total
+ elif task_id is not None:
+ with self._lock:
+ total_bytes = self._tasks[task_id].total
+ if total_bytes is None:
+ raise ValueError(
+ f"unable to get the total number of bytes, please specify 'total'"
+ )
+
+ # update total of task or create new task
+ if task_id is None:
+ task_id = self.add_task(description, total=total_bytes)
+ else:
+ self.update(task_id, total=total_bytes)
+
+ return _Reader(file, self, task_id, close_handle=False)
+
+ @typing.overload
+ def open(
+ self,
+ file: Union[str, "PathLike[str]", bytes],
+ mode: Literal["rb"],
+ buffering: int = -1,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ newline: Optional[str] = None,
+ *,
+ total: Optional[int] = None,
+ task_id: Optional[TaskID] = None,
+ description: str = "Reading...",
+ ) -> BinaryIO:
+ pass
+
+ @typing.overload
+ def open(
+ self,
+ file: Union[str, "PathLike[str]", bytes],
+ mode: Union[Literal["r"], Literal["rt"]],
+ buffering: int = -1,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ newline: Optional[str] = None,
+ *,
+ total: Optional[int] = None,
+ task_id: Optional[TaskID] = None,
+ description: str = "Reading...",
+ ) -> TextIO:
+ pass
+
+ def open(
+ self,
+ file: Union[str, "PathLike[str]", bytes],
+ mode: Union[Literal["rb"], Literal["rt"], Literal["r"]] = "r",
+ buffering: int = -1,
+ encoding: Optional[str] = None,
+ errors: Optional[str] = None,
+ newline: Optional[str] = None,
+ *,
+ total: Optional[int] = None,
+ task_id: Optional[TaskID] = None,
+ description: str = "Reading...",
+ ) -> Union[BinaryIO, TextIO]:
+ """Track progress while reading from a binary file.
+
+ Args:
+ path (Union[str, PathLike[str]]): The path to the file to read.
+ mode (str): The mode to use to open the file. Only supports "r", "rb" or "rt".
+ buffering (int): The buffering strategy to use, see :func:`io.open`.
+ encoding (str, optional): The encoding to use when reading in text mode, see :func:`io.open`.
+ errors (str, optional): The error handling strategy for decoding errors, see :func:`io.open`.
+ newline (str, optional): The strategy for handling newlines in text mode, see :func:`io.open`.
+ total (int, optional): Total number of bytes to read. If none given, os.stat(path).st_size is used.
+ task_id (TaskID): Task to track. Default is new task.
+ description (str, optional): Description of task, if new task is created.
+
+ Returns:
+ BinaryIO: A readable file-like object in binary mode.
+
+ Raises:
+ ValueError: When an invalid mode is given.
+ """
+ # normalize the mode (always rb, rt)
+ _mode = "".join(sorted(mode, reverse=False))
+ if _mode not in ("br", "rt", "r"):
+ raise ValueError("invalid mode {!r}".format(mode))
+
+ # patch buffering to provide the same behaviour as the builtin `open`
+ line_buffering = buffering == 1
+ if _mode == "br" and buffering == 1:
+ warnings.warn(
+ "line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used",
+ RuntimeWarning,
+ )
+ buffering = -1
+ elif _mode == "rt" or _mode == "r":
+ if buffering == 0:
+ raise ValueError("can't have unbuffered text I/O")
+ elif buffering == 1:
+ buffering = -1
+
+ # attempt to get the total with `os.stat`
+ if total is None:
+ total = stat(file).st_size
+
+ # update total of task or create new task
+ if task_id is None:
+ task_id = self.add_task(description, total=total)
+ else:
+ self.update(task_id, total=total)
+
+ # open the file in binary mode,
+ handle = io.open(file, "rb", buffering=buffering)
+ reader = _Reader(handle, self, task_id, close_handle=True)
+
+ # wrap the reader in a `TextIOWrapper` if text mode
+ if mode == "r" or mode == "rt":
+ return io.TextIOWrapper(
+ reader,
+ encoding=encoding,
+ errors=errors,
+ newline=newline,
+ line_buffering=line_buffering,
+ )
+
+ return reader
+
+ def start_task(self, task_id: TaskID) -> None:
+ """Start a task.
+
+ Starts a task (used when calculating elapsed time). You may need to call this manually,
+ if you called ``add_task`` with ``start=False``.
+
+ Args:
+ task_id (TaskID): ID of task.
+ """
+ with self._lock:
+ task = self._tasks[task_id]
+ if task.start_time is None:
+ task.start_time = self.get_time()
+
+ def stop_task(self, task_id: TaskID) -> None:
+ """Stop a task.
+
+ This will freeze the elapsed time on the task.
+
+ Args:
+ task_id (TaskID): ID of task.
+ """
+ with self._lock:
+ task = self._tasks[task_id]
+ current_time = self.get_time()
+ if task.start_time is None:
+ task.start_time = current_time
+ task.stop_time = current_time
+
+ def update(
+ self,
+ task_id: TaskID,
+ *,
+ total: Optional[float] = None,
+ completed: Optional[float] = None,
+ advance: Optional[float] = None,
+ description: Optional[str] = None,
+ visible: Optional[bool] = None,
+ refresh: bool = False,
+ **fields: Any,
+ ) -> None:
+ """Update information associated with a task.
+
+ Args:
+ task_id (TaskID): Task id (returned by add_task).
+ total (float, optional): Updates task.total if not None.
+ completed (float, optional): Updates task.completed if not None.
+ advance (float, optional): Add a value to task.completed if not None.
+ description (str, optional): Change task description if not None.
+ visible (bool, optional): Set visible flag if not None.
+ refresh (bool): Force a refresh of progress information. Default is False.
+ **fields (Any): Additional data fields required for rendering.
+ """
+ with self._lock:
+ task = self._tasks[task_id]
+ completed_start = task.completed
+
+ if total is not None and total != task.total:
+ task.total = total
+ task._reset()
+ if advance is not None:
+ task.completed += advance
+ if completed is not None:
+ task.completed = completed
+ if description is not None:
+ task.description = description
+ if visible is not None:
+ task.visible = visible
+ task.fields.update(fields)
+ update_completed = task.completed - completed_start
+
+ current_time = self.get_time()
+ old_sample_time = current_time - self.speed_estimate_period
+ _progress = task._progress
+
+ popleft = _progress.popleft
+ while _progress and _progress[0].timestamp < old_sample_time:
+ popleft()
+ if update_completed > 0:
+ _progress.append(ProgressSample(current_time, update_completed))
+ if (
+ task.total is not None
+ and task.completed >= task.total
+ and task.finished_time is None
+ ):
+ task.finished_time = task.elapsed
+
+ if refresh:
+ self.refresh()
+
+ def reset(
+ self,
+ task_id: TaskID,
+ *,
+ start: bool = True,
+ total: Optional[float] = None,
+ completed: int = 0,
+ visible: Optional[bool] = None,
+ description: Optional[str] = None,
+ **fields: Any,
+ ) -> None:
+ """Reset a task so completed is 0 and the clock is reset.
+
+ Args:
+ task_id (TaskID): ID of task.
+ start (bool, optional): Start the task after reset. Defaults to True.
+ total (float, optional): New total steps in task, or None to use current total. Defaults to None.
+ completed (int, optional): Number of steps completed. Defaults to 0.
+ visible (bool, optional): Enable display of the task. Defaults to True.
+ description (str, optional): Change task description if not None. Defaults to None.
+ **fields (str): Additional data fields required for rendering.
+ """
+ current_time = self.get_time()
+ with self._lock:
+ task = self._tasks[task_id]
+ task._reset()
+ task.start_time = current_time if start else None
+ if total is not None:
+ task.total = total
+ task.completed = completed
+ if visible is not None:
+ task.visible = visible
+ if fields:
+ task.fields = fields
+ if description is not None:
+ task.description = description
+ task.finished_time = None
+ self.refresh()
+
+ def advance(self, task_id: TaskID, advance: float = 1) -> None:
+ """Advance task by a number of steps.
+
+ Args:
+ task_id (TaskID): ID of task.
+ advance (float): Number of steps to advance. Default is 1.
+ """
+ current_time = self.get_time()
+ with self._lock:
+ task = self._tasks[task_id]
+ completed_start = task.completed
+ task.completed += advance
+ update_completed = task.completed - completed_start
+ old_sample_time = current_time - self.speed_estimate_period
+ _progress = task._progress
+
+ popleft = _progress.popleft
+ while _progress and _progress[0].timestamp < old_sample_time:
+ popleft()
+ while len(_progress) > 1000:
+ popleft()
+ _progress.append(ProgressSample(current_time, update_completed))
+ if (
+ task.total is not None
+ and task.completed >= task.total
+ and task.finished_time is None
+ ):
+ task.finished_time = task.elapsed
+ task.finished_speed = task.speed
+
+ def refresh(self) -> None:
+ """Refresh (render) the progress information."""
+ if not self.disable and self.live.is_started:
+ self.live.refresh()
+
+ def get_renderable(self) -> RenderableType:
+ """Get a renderable for the progress display."""
+ renderable = Group(*self.get_renderables())
+ return renderable
+
+ def get_renderables(self) -> Iterable[RenderableType]:
+ """Get a number of renderables for the progress display."""
+ table = self.make_tasks_table(self.tasks)
+ yield table
+
+ def make_tasks_table(self, tasks: Iterable[Task]) -> Table:
+ """Get a table to render the Progress display.
+
+ Args:
+ tasks (Iterable[Task]): An iterable of Task instances, one per row of the table.
+
+ Returns:
+ Table: A table instance.
+ """
+ table_columns = (
+ (
+ Column(no_wrap=True)
+ if isinstance(_column, str)
+ else _column.get_table_column().copy()
+ )
+ for _column in self.columns
+ )
+ table = Table.grid(*table_columns, padding=(0, 1), expand=self.expand)
+
+ for task in tasks:
+ if task.visible:
+ table.add_row(
+ *(
+ (
+ column.format(task=task)
+ if isinstance(column, str)
+ else column(task)
+ )
+ for column in self.columns
+ )
+ )
+ return table
+
+ def __rich__(self) -> RenderableType:
+ """Makes the Progress class itself renderable."""
+ with self._lock:
+ return self.get_renderable()
+
+ def add_task(
+ self,
+ description: str,
+ start: bool = True,
+ total: Optional[float] = 100.0,
+ completed: int = 0,
+ visible: bool = True,
+ **fields: Any,
+ ) -> TaskID:
+ """Add a new 'task' to the Progress display.
+
+ Args:
+ description (str): A description of the task.
+ start (bool, optional): Start the task immediately (to calculate elapsed time). If set to False,
+ you will need to call `start` manually. Defaults to True.
+ total (float, optional): Number of total steps in the progress if known.
+ Set to None to render a pulsing animation. Defaults to 100.
+ completed (int, optional): Number of steps completed so far. Defaults to 0.
+ visible (bool, optional): Enable display of the task. Defaults to True.
+ **fields (str): Additional data fields required for rendering.
+
+ Returns:
+ TaskID: An ID you can use when calling `update`.
+ """
+ with self._lock:
+ task = Task(
+ self._task_index,
+ description,
+ total,
+ completed,
+ visible=visible,
+ fields=fields,
+ _get_time=self.get_time,
+ _lock=self._lock,
+ )
+ self._tasks[self._task_index] = task
+ if start:
+ self.start_task(self._task_index)
+ new_task_index = self._task_index
+ self._task_index = TaskID(int(self._task_index) + 1)
+ self.refresh()
+ return new_task_index
+
+ def remove_task(self, task_id: TaskID) -> None:
+ """Delete a task if it exists.
+
+ Args:
+ task_id (TaskID): A task ID.
+
+ """
+ with self._lock:
+ del self._tasks[task_id]
+
+
+if __name__ == "__main__": # pragma: no coverage
+
+ import random
+ import time
+
+ from .panel import Panel
+ from .rule import Rule
+ from .syntax import Syntax
+ from .table import Table
+
+ syntax = Syntax(
+ '''def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
+ """Iterate and generate a tuple with a flag for last value."""
+ iter_values = iter(values)
+ try:
+ previous_value = next(iter_values)
+ except StopIteration:
+ return
+ for value in iter_values:
+ yield False, previous_value
+ previous_value = value
+ yield True, previous_value''',
+ "python",
+ line_numbers=True,
+ )
+
+ table = Table("foo", "bar", "baz")
+ table.add_row("1", "2", "3")
+
+ progress_renderables = [
+ "Text may be printed while the progress bars are rendering.",
+ Panel("In fact, [i]any[/i] renderable will work"),
+ "Such as [magenta]tables[/]...",
+ table,
+ "Pretty printed structures...",
+ {"type": "example", "text": "Pretty printed"},
+ "Syntax...",
+ syntax,
+ Rule("Give it a try!"),
+ ]
+
+ from itertools import cycle
+
+ examples = cycle(progress_renderables)
+
+ console = Console(record=True)
+
+ with Progress(
+ SpinnerColumn(),
+ *Progress.get_default_columns(),
+ TimeElapsedColumn(),
+ console=console,
+ transient=False,
+ ) as progress:
+
+ task1 = progress.add_task("[red]Downloading", total=1000)
+ task2 = progress.add_task("[green]Processing", total=1000)
+ task3 = progress.add_task("[yellow]Thinking", total=None)
+
+ while not progress.finished:
+ progress.update(task1, advance=0.5)
+ progress.update(task2, advance=0.3)
+ time.sleep(0.01)
+ if random.randint(0, 100) < 1:
+ progress.log(next(examples))
diff --git a/src/pip/_vendor/rich/progress_bar.py b/src/pip/_vendor/rich/progress_bar.py
new file mode 100644
index 000000000..67361df2e
--- /dev/null
+++ b/src/pip/_vendor/rich/progress_bar.py
@@ -0,0 +1,224 @@
+import math
+from functools import lru_cache
+from time import monotonic
+from typing import Iterable, List, Optional
+
+from .color import Color, blend_rgb
+from .color_triplet import ColorTriplet
+from .console import Console, ConsoleOptions, RenderResult
+from .jupyter import JupyterMixin
+from .measure import Measurement
+from .segment import Segment
+from .style import Style, StyleType
+
+# Number of characters before 'pulse' animation repeats
+PULSE_SIZE = 20
+
+
+class ProgressBar(JupyterMixin):
+ """Renders a (progress) bar. Used by rich.progress.
+
+ Args:
+ total (float, optional): Number of steps in the bar. Defaults to 100. Set to None to render a pulsing animation.
+ completed (float, optional): Number of steps completed. Defaults to 0.
+ width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None.
+ pulse (bool, optional): Enable pulse effect. Defaults to False. Will pulse if a None total was passed.
+ style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
+ complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
+ finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.finished".
+ pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
+ animation_time (Optional[float], optional): Time in seconds to use for animation, or None to use system time.
+ """
+
+ def __init__(
+ self,
+ total: Optional[float] = 100.0,
+ completed: float = 0,
+ width: Optional[int] = None,
+ pulse: bool = False,
+ style: StyleType = "bar.back",
+ complete_style: StyleType = "bar.complete",
+ finished_style: StyleType = "bar.finished",
+ pulse_style: StyleType = "bar.pulse",
+ animation_time: Optional[float] = None,
+ ):
+ self.total = total
+ self.completed = completed
+ self.width = width
+ self.pulse = pulse
+ self.style = style
+ self.complete_style = complete_style
+ self.finished_style = finished_style
+ self.pulse_style = pulse_style
+ self.animation_time = animation_time
+
+ self._pulse_segments: Optional[List[Segment]] = None
+
+ def __repr__(self) -> str:
+ return f"<Bar {self.completed!r} of {self.total!r}>"
+
+ @property
+ def percentage_completed(self) -> Optional[float]:
+ """Calculate percentage complete."""
+ if self.total is None:
+ return None
+ completed = (self.completed / self.total) * 100.0
+ completed = min(100, max(0.0, completed))
+ return completed
+
+ @lru_cache(maxsize=16)
+ def _get_pulse_segments(
+ self,
+ fore_style: Style,
+ back_style: Style,
+ color_system: str,
+ no_color: bool,
+ ascii: bool = False,
+ ) -> List[Segment]:
+ """Get a list of segments to render a pulse animation.
+
+ Returns:
+ List[Segment]: A list of segments, one segment per character.
+ """
+ bar = "-" if ascii else "━"
+ segments: List[Segment] = []
+ if color_system not in ("standard", "eight_bit", "truecolor") or no_color:
+ segments += [Segment(bar, fore_style)] * (PULSE_SIZE // 2)
+ segments += [Segment(" " if no_color else bar, back_style)] * (
+ PULSE_SIZE - (PULSE_SIZE // 2)
+ )
+ return segments
+
+ append = segments.append
+ fore_color = (
+ fore_style.color.get_truecolor()
+ if fore_style.color
+ else ColorTriplet(255, 0, 255)
+ )
+ back_color = (
+ back_style.color.get_truecolor()
+ if back_style.color
+ else ColorTriplet(0, 0, 0)
+ )
+ cos = math.cos
+ pi = math.pi
+ _Segment = Segment
+ _Style = Style
+ from_triplet = Color.from_triplet
+
+ for index in range(PULSE_SIZE):
+ position = index / PULSE_SIZE
+ fade = 0.5 + cos((position * pi * 2)) / 2.0
+ color = blend_rgb(fore_color, back_color, cross_fade=fade)
+ append(_Segment(bar, _Style(color=from_triplet(color))))
+ return segments
+
+ def update(self, completed: float, total: Optional[float] = None) -> None:
+ """Update progress with new values.
+
+ Args:
+ completed (float): Number of steps completed.
+ total (float, optional): Total number of steps, or ``None`` to not change. Defaults to None.
+ """
+ self.completed = completed
+ self.total = total if total is not None else self.total
+
+ def _render_pulse(
+ self, console: Console, width: int, ascii: bool = False
+ ) -> Iterable[Segment]:
+ """Renders the pulse animation.
+
+ Args:
+ console (Console): Console instance.
+ width (int): Width in characters of pulse animation.
+
+ Returns:
+ RenderResult: [description]
+
+ Yields:
+ Iterator[Segment]: Segments to render pulse
+ """
+ fore_style = console.get_style(self.pulse_style, default="white")
+ back_style = console.get_style(self.style, default="black")
+
+ pulse_segments = self._get_pulse_segments(
+ fore_style, back_style, console.color_system, console.no_color, ascii=ascii
+ )
+ segment_count = len(pulse_segments)
+ current_time = (
+ monotonic() if self.animation_time is None else self.animation_time
+ )
+ segments = pulse_segments * (int(width / segment_count) + 2)
+ offset = int(-current_time * 15) % segment_count
+ segments = segments[offset : offset + width]
+ yield from segments
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+
+ width = min(self.width or options.max_width, options.max_width)
+ ascii = options.legacy_windows or options.ascii_only
+ should_pulse = self.pulse or self.total is None
+ if should_pulse:
+ yield from self._render_pulse(console, width, ascii=ascii)
+ return
+
+ completed: Optional[float] = (
+ min(self.total, max(0, self.completed)) if self.total is not None else None
+ )
+
+ bar = "-" if ascii else "━"
+ half_bar_right = " " if ascii else "╸"
+ half_bar_left = " " if ascii else "╺"
+ complete_halves = (
+ int(width * 2 * completed / self.total)
+ if self.total and completed is not None
+ else width * 2
+ )
+ bar_count = complete_halves // 2
+ half_bar_count = complete_halves % 2
+ style = console.get_style(self.style)
+ is_finished = self.total is None or self.completed >= self.total
+ complete_style = console.get_style(
+ self.finished_style if is_finished else self.complete_style
+ )
+ _Segment = Segment
+ if bar_count:
+ yield _Segment(bar * bar_count, complete_style)
+ if half_bar_count:
+ yield _Segment(half_bar_right * half_bar_count, complete_style)
+
+ if not console.no_color:
+ remaining_bars = width - bar_count - half_bar_count
+ if remaining_bars and console.color_system is not None:
+ if not half_bar_count and bar_count:
+ yield _Segment(half_bar_left, style)
+ remaining_bars -= 1
+ if remaining_bars:
+ yield _Segment(bar * remaining_bars, style)
+
+ def __rich_measure__(
+ self, console: Console, options: ConsoleOptions
+ ) -> Measurement:
+ return (
+ Measurement(self.width, self.width)
+ if self.width is not None
+ else Measurement(4, options.max_width)
+ )
+
+
+if __name__ == "__main__": # pragma: no cover
+ console = Console()
+ bar = ProgressBar(width=50, total=100)
+
+ import time
+
+ console.show_cursor(False)
+ for n in range(0, 101, 1):
+ bar.update(n)
+ console.print(bar)
+ console.file.write("\r")
+ time.sleep(0.05)
+ console.show_cursor(True)
+ console.print()
diff --git a/src/pip/_vendor/rich/prompt.py b/src/pip/_vendor/rich/prompt.py
new file mode 100644
index 000000000..2bd0a7724
--- /dev/null
+++ b/src/pip/_vendor/rich/prompt.py
@@ -0,0 +1,376 @@
+from typing import Any, Generic, List, Optional, TextIO, TypeVar, Union, overload
+
+from . import get_console
+from .console import Console
+from .text import Text, TextType
+
+PromptType = TypeVar("PromptType")
+DefaultType = TypeVar("DefaultType")
+
+
+class PromptError(Exception):
+ """Exception base class for prompt related errors."""
+
+
+class InvalidResponse(PromptError):
+ """Exception to indicate a response was invalid. Raise this within process_response() to indicate an error
+ and provide an error message.
+
+ Args:
+ message (Union[str, Text]): Error message.
+ """
+
+ def __init__(self, message: TextType) -> None:
+ self.message = message
+
+ def __rich__(self) -> TextType:
+ return self.message
+
+
+class PromptBase(Generic[PromptType]):
+ """Ask the user for input until a valid response is received. This is the base class, see one of
+ the concrete classes for examples.
+
+ Args:
+ prompt (TextType, optional): Prompt text. Defaults to "".
+ console (Console, optional): A Console instance or None to use global console. Defaults to None.
+ password (bool, optional): Enable password input. Defaults to False.
+ choices (List[str], optional): A list of valid choices. Defaults to None.
+ show_default (bool, optional): Show default in prompt. Defaults to True.
+ show_choices (bool, optional): Show choices in prompt. Defaults to True.
+ """
+
+ response_type: type = str
+
+ validate_error_message = "[prompt.invalid]Please enter a valid value"
+ illegal_choice_message = (
+ "[prompt.invalid.choice]Please select one of the available options"
+ )
+ prompt_suffix = ": "
+
+ choices: Optional[List[str]] = None
+
+ def __init__(
+ self,
+ prompt: TextType = "",
+ *,
+ console: Optional[Console] = None,
+ password: bool = False,
+ choices: Optional[List[str]] = None,
+ show_default: bool = True,
+ show_choices: bool = True,
+ ) -> None:
+ self.console = console or get_console()
+ self.prompt = (
+ Text.from_markup(prompt, style="prompt")
+ if isinstance(prompt, str)
+ else prompt
+ )
+ self.password = password
+ if choices is not None:
+ self.choices = choices
+ self.show_default = show_default
+ self.show_choices = show_choices
+
+ @classmethod
+ @overload
+ def ask(
+ cls,
+ prompt: TextType = "",
+ *,
+ console: Optional[Console] = None,
+ password: bool = False,
+ choices: Optional[List[str]] = None,
+ show_default: bool = True,
+ show_choices: bool = True,
+ default: DefaultType,
+ stream: Optional[TextIO] = None,
+ ) -> Union[DefaultType, PromptType]:
+ ...
+
+ @classmethod
+ @overload
+ def ask(
+ cls,
+ prompt: TextType = "",
+ *,
+ console: Optional[Console] = None,
+ password: bool = False,
+ choices: Optional[List[str]] = None,
+ show_default: bool = True,
+ show_choices: bool = True,
+ stream: Optional[TextIO] = None,
+ ) -> PromptType:
+ ...
+
+ @classmethod
+ def ask(
+ cls,
+ prompt: TextType = "",
+ *,
+ console: Optional[Console] = None,
+ password: bool = False,
+ choices: Optional[List[str]] = None,
+ show_default: bool = True,
+ show_choices: bool = True,
+ default: Any = ...,
+ stream: Optional[TextIO] = None,
+ ) -> Any:
+ """Shortcut to construct and run a prompt loop and return the result.
+
+ Example:
+ >>> filename = Prompt.ask("Enter a filename")
+
+ Args:
+ prompt (TextType, optional): Prompt text. Defaults to "".
+ console (Console, optional): A Console instance or None to use global console. Defaults to None.
+ password (bool, optional): Enable password input. Defaults to False.
+ choices (List[str], optional): A list of valid choices. Defaults to None.
+ show_default (bool, optional): Show default in prompt. Defaults to True.
+ show_choices (bool, optional): Show choices in prompt. Defaults to True.
+ stream (TextIO, optional): Optional text file open for reading to get input. Defaults to None.
+ """
+ _prompt = cls(
+ prompt,
+ console=console,
+ password=password,
+ choices=choices,
+ show_default=show_default,
+ show_choices=show_choices,
+ )
+ return _prompt(default=default, stream=stream)
+
+ def render_default(self, default: DefaultType) -> Text:
+ """Turn the supplied default in to a Text instance.
+
+ Args:
+ default (DefaultType): Default value.
+
+ Returns:
+ Text: Text containing rendering of default value.
+ """
+ return Text(f"({default})", "prompt.default")
+
+ def make_prompt(self, default: DefaultType) -> Text:
+ """Make prompt text.
+
+ Args:
+ default (DefaultType): Default value.
+
+ Returns:
+ Text: Text to display in prompt.
+ """
+ prompt = self.prompt.copy()
+ prompt.end = ""
+
+ if self.show_choices and self.choices:
+ _choices = "/".join(self.choices)
+ choices = f"[{_choices}]"
+ prompt.append(" ")
+ prompt.append(choices, "prompt.choices")
+
+ if (
+ default != ...
+ and self.show_default
+ and isinstance(default, (str, self.response_type))
+ ):
+ prompt.append(" ")
+ _default = self.render_default(default)
+ prompt.append(_default)
+
+ prompt.append(self.prompt_suffix)
+
+ return prompt
+
+ @classmethod
+ def get_input(
+ cls,
+ console: Console,
+ prompt: TextType,
+ password: bool,
+ stream: Optional[TextIO] = None,
+ ) -> str:
+ """Get input from user.
+
+ Args:
+ console (Console): Console instance.
+ prompt (TextType): Prompt text.
+ password (bool): Enable password entry.
+
+ Returns:
+ str: String from user.
+ """
+ return console.input(prompt, password=password, stream=stream)
+
+ def check_choice(self, value: str) -> bool:
+ """Check value is in the list of valid choices.
+
+ Args:
+ value (str): Value entered by user.
+
+ Returns:
+ bool: True if choice was valid, otherwise False.
+ """
+ assert self.choices is not None
+ return value.strip() in self.choices
+
+ def process_response(self, value: str) -> PromptType:
+ """Process response from user, convert to prompt type.
+
+ Args:
+ value (str): String typed by user.
+
+ Raises:
+ InvalidResponse: If ``value`` is invalid.
+
+ Returns:
+ PromptType: The value to be returned from ask method.
+ """
+ value = value.strip()
+ try:
+ return_value: PromptType = self.response_type(value)
+ except ValueError:
+ raise InvalidResponse(self.validate_error_message)
+
+ if self.choices is not None and not self.check_choice(value):
+ raise InvalidResponse(self.illegal_choice_message)
+
+ return return_value
+
+ def on_validate_error(self, value: str, error: InvalidResponse) -> None:
+ """Called to handle validation error.
+
+ Args:
+ value (str): String entered by user.
+ error (InvalidResponse): Exception instance the initiated the error.
+ """
+ self.console.print(error)
+
+ def pre_prompt(self) -> None:
+ """Hook to display something before the prompt."""
+
+ @overload
+ def __call__(self, *, stream: Optional[TextIO] = None) -> PromptType:
+ ...
+
+ @overload
+ def __call__(
+ self, *, default: DefaultType, stream: Optional[TextIO] = None
+ ) -> Union[PromptType, DefaultType]:
+ ...
+
+ def __call__(self, *, default: Any = ..., stream: Optional[TextIO] = None) -> Any:
+ """Run the prompt loop.
+
+ Args:
+ default (Any, optional): Optional default value.
+
+ Returns:
+ PromptType: Processed value.
+ """
+ while True:
+ self.pre_prompt()
+ prompt = self.make_prompt(default)
+ value = self.get_input(self.console, prompt, self.password, stream=stream)
+ if value == "" and default != ...:
+ return default
+ try:
+ return_value = self.process_response(value)
+ except InvalidResponse as error:
+ self.on_validate_error(value, error)
+ continue
+ else:
+ return return_value
+
+
+class Prompt(PromptBase[str]):
+ """A prompt that returns a str.
+
+ Example:
+ >>> name = Prompt.ask("Enter your name")
+
+
+ """
+
+ response_type = str
+
+
+class IntPrompt(PromptBase[int]):
+ """A prompt that returns an integer.
+
+ Example:
+ >>> burrito_count = IntPrompt.ask("How many burritos do you want to order")
+
+ """
+
+ response_type = int
+ validate_error_message = "[prompt.invalid]Please enter a valid integer number"
+
+
+class FloatPrompt(PromptBase[int]):
+ """A prompt that returns a float.
+
+ Example:
+ >>> temperature = FloatPrompt.ask("Enter desired temperature")
+
+ """
+
+ response_type = float
+ validate_error_message = "[prompt.invalid]Please enter a number"
+
+
+class Confirm(PromptBase[bool]):
+ """A yes / no confirmation prompt.
+
+ Example:
+ >>> if Confirm.ask("Continue"):
+ run_job()
+
+ """
+
+ response_type = bool
+ validate_error_message = "[prompt.invalid]Please enter Y or N"
+ choices: List[str] = ["y", "n"]
+
+ def render_default(self, default: DefaultType) -> Text:
+ """Render the default as (y) or (n) rather than True/False."""
+ yes, no = self.choices
+ return Text(f"({yes})" if default else f"({no})", style="prompt.default")
+
+ def process_response(self, value: str) -> bool:
+ """Convert choices to a bool."""
+ value = value.strip().lower()
+ if value not in self.choices:
+ raise InvalidResponse(self.validate_error_message)
+ return value == self.choices[0]
+
+
+if __name__ == "__main__": # pragma: no cover
+
+ from pip._vendor.rich import print
+
+ if Confirm.ask("Run [i]prompt[/i] tests?", default=True):
+ while True:
+ result = IntPrompt.ask(
+ ":rocket: Enter a number between [b]1[/b] and [b]10[/b]", default=5
+ )
+ if result >= 1 and result <= 10:
+ break
+ print(":pile_of_poo: [prompt.invalid]Number must be between 1 and 10")
+ print(f"number={result}")
+
+ while True:
+ password = Prompt.ask(
+ "Please enter a password [cyan](must be at least 5 characters)",
+ password=True,
+ )
+ if len(password) >= 5:
+ break
+ print("[prompt.invalid]password too short")
+ print(f"password={password!r}")
+
+ fruit = Prompt.ask("Enter a fruit", choices=["apple", "orange", "pear"])
+ print(f"fruit={fruit!r}")
+
+ else:
+ print("[b]OK :loudly_crying_face:")
diff --git a/src/pip/_vendor/rich/protocol.py b/src/pip/_vendor/rich/protocol.py
new file mode 100644
index 000000000..12ab23713
--- /dev/null
+++ b/src/pip/_vendor/rich/protocol.py
@@ -0,0 +1,42 @@
+from typing import Any, cast, Set, TYPE_CHECKING
+from inspect import isclass
+
+if TYPE_CHECKING:
+ from pip._vendor.rich.console import RenderableType
+
+_GIBBERISH = """aihwerij235234ljsdnp34ksodfipwoe234234jlskjdf"""
+
+
+def is_renderable(check_object: Any) -> bool:
+ """Check if an object may be rendered by Rich."""
+ return (
+ isinstance(check_object, str)
+ or hasattr(check_object, "__rich__")
+ or hasattr(check_object, "__rich_console__")
+ )
+
+
+def rich_cast(renderable: object) -> "RenderableType":
+ """Cast an object to a renderable by calling __rich__ if present.
+
+ Args:
+ renderable (object): A potentially renderable object
+
+ Returns:
+ object: The result of recursively calling __rich__.
+ """
+ from pip._vendor.rich.console import RenderableType
+
+ rich_visited_set: Set[type] = set() # Prevent potential infinite loop
+ while hasattr(renderable, "__rich__") and not isclass(renderable):
+ # Detect object which claim to have all the attributes
+ if hasattr(renderable, _GIBBERISH):
+ return repr(renderable)
+ cast_method = getattr(renderable, "__rich__")
+ renderable = cast_method()
+ renderable_type = type(renderable)
+ if renderable_type in rich_visited_set:
+ break
+ rich_visited_set.add(renderable_type)
+
+ return cast(RenderableType, renderable)
diff --git a/src/pip/_vendor/rich/py.typed b/src/pip/_vendor/rich/py.typed
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/pip/_vendor/rich/py.typed
diff --git a/src/pip/_vendor/rich/region.py b/src/pip/_vendor/rich/region.py
new file mode 100644
index 000000000..75b3631c3
--- /dev/null
+++ b/src/pip/_vendor/rich/region.py
@@ -0,0 +1,10 @@
+from typing import NamedTuple
+
+
+class Region(NamedTuple):
+ """Defines a rectangular region of the screen."""
+
+ x: int
+ y: int
+ width: int
+ height: int
diff --git a/src/pip/_vendor/rich/repr.py b/src/pip/_vendor/rich/repr.py
new file mode 100644
index 000000000..72d1a7e30
--- /dev/null
+++ b/src/pip/_vendor/rich/repr.py
@@ -0,0 +1,149 @@
+import inspect
+from functools import partial
+from typing import (
+ Any,
+ Callable,
+ Iterable,
+ List,
+ Optional,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+ overload,
+)
+
+T = TypeVar("T")
+
+
+Result = Iterable[Union[Any, Tuple[Any], Tuple[str, Any], Tuple[str, Any, Any]]]
+RichReprResult = Result
+
+
+class ReprError(Exception):
+ """An error occurred when attempting to build a repr."""
+
+
+@overload
+def auto(cls: Optional[Type[T]]) -> Type[T]:
+ ...
+
+
+@overload
+def auto(*, angular: bool = False) -> Callable[[Type[T]], Type[T]]:
+ ...
+
+
+def auto(
+ cls: Optional[Type[T]] = None, *, angular: Optional[bool] = None
+) -> Union[Type[T], Callable[[Type[T]], Type[T]]]:
+ """Class decorator to create __repr__ from __rich_repr__"""
+
+ def do_replace(cls: Type[T], angular: Optional[bool] = None) -> Type[T]:
+ def auto_repr(self: T) -> str:
+ """Create repr string from __rich_repr__"""
+ repr_str: List[str] = []
+ append = repr_str.append
+
+ angular: bool = getattr(self.__rich_repr__, "angular", False) # type: ignore[attr-defined]
+ for arg in self.__rich_repr__(): # type: ignore[attr-defined]
+ if isinstance(arg, tuple):
+ if len(arg) == 1:
+ append(repr(arg[0]))
+ else:
+ key, value, *default = arg
+ if key is None:
+ append(repr(value))
+ else:
+ if len(default) and default[0] == value:
+ continue
+ append(f"{key}={value!r}")
+ else:
+ append(repr(arg))
+ if angular:
+ return f"<{self.__class__.__name__} {' '.join(repr_str)}>"
+ else:
+ return f"{self.__class__.__name__}({', '.join(repr_str)})"
+
+ def auto_rich_repr(self: Type[T]) -> Result:
+ """Auto generate __rich_rep__ from signature of __init__"""
+ try:
+ signature = inspect.signature(self.__init__)
+ for name, param in signature.parameters.items():
+ if param.kind == param.POSITIONAL_ONLY:
+ yield getattr(self, name)
+ elif param.kind in (
+ param.POSITIONAL_OR_KEYWORD,
+ param.KEYWORD_ONLY,
+ ):
+ if param.default == param.empty:
+ yield getattr(self, param.name)
+ else:
+ yield param.name, getattr(self, param.name), param.default
+ except Exception as error:
+ raise ReprError(
+ f"Failed to auto generate __rich_repr__; {error}"
+ ) from None
+
+ if not hasattr(cls, "__rich_repr__"):
+ auto_rich_repr.__doc__ = "Build a rich repr"
+ cls.__rich_repr__ = auto_rich_repr # type: ignore[attr-defined]
+
+ auto_repr.__doc__ = "Return repr(self)"
+ cls.__repr__ = auto_repr # type: ignore[assignment]
+ if angular is not None:
+ cls.__rich_repr__.angular = angular # type: ignore[attr-defined]
+ return cls
+
+ if cls is None:
+ return partial(do_replace, angular=angular)
+ else:
+ return do_replace(cls, angular=angular)
+
+
+@overload
+def rich_repr(cls: Optional[Type[T]]) -> Type[T]:
+ ...
+
+
+@overload
+def rich_repr(*, angular: bool = False) -> Callable[[Type[T]], Type[T]]:
+ ...
+
+
+def rich_repr(
+ cls: Optional[Type[T]] = None, *, angular: bool = False
+) -> Union[Type[T], Callable[[Type[T]], Type[T]]]:
+ if cls is None:
+ return auto(angular=angular)
+ else:
+ return auto(cls)
+
+
+if __name__ == "__main__":
+
+ @auto
+ class Foo:
+ def __rich_repr__(self) -> Result:
+ yield "foo"
+ yield "bar", {"shopping": ["eggs", "ham", "pineapple"]}
+ yield "buy", "hand sanitizer"
+
+ foo = Foo()
+ from pip._vendor.rich.console import Console
+
+ console = Console()
+
+ console.rule("Standard repr")
+ console.print(foo)
+
+ console.print(foo, width=60)
+ console.print(foo, width=30)
+
+ console.rule("Angular repr")
+ Foo.__rich_repr__.angular = True # type: ignore[attr-defined]
+
+ console.print(foo)
+
+ console.print(foo, width=60)
+ console.print(foo, width=30)
diff --git a/src/pip/_vendor/rich/rule.py b/src/pip/_vendor/rich/rule.py
new file mode 100644
index 000000000..0b78f7a4e
--- /dev/null
+++ b/src/pip/_vendor/rich/rule.py
@@ -0,0 +1,134 @@
+from typing import Union
+
+from .align import AlignMethod
+from .cells import cell_len, set_cell_size
+from .console import Console, ConsoleOptions, RenderResult
+from .jupyter import JupyterMixin
+from .measure import Measurement
+from .style import Style
+from .text import Text
+
+
+class Rule(JupyterMixin):
+ """A console renderable to draw a horizontal rule (line).
+
+ Args:
+ title (Union[str, Text], optional): Text to render in the rule. Defaults to "".
+ characters (str, optional): Character(s) used to draw the line. Defaults to "─".
+ style (StyleType, optional): Style of Rule. Defaults to "rule.line".
+ end (str, optional): Character at end of Rule. defaults to "\\\\n"
+ align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center".
+ """
+
+ def __init__(
+ self,
+ title: Union[str, Text] = "",
+ *,
+ characters: str = "─",
+ style: Union[str, Style] = "rule.line",
+ end: str = "\n",
+ align: AlignMethod = "center",
+ ) -> None:
+ if cell_len(characters) < 1:
+ raise ValueError(
+ "'characters' argument must have a cell width of at least 1"
+ )
+ if align not in ("left", "center", "right"):
+ raise ValueError(
+ f'invalid value for align, expected "left", "center", "right" (not {align!r})'
+ )
+ self.title = title
+ self.characters = characters
+ self.style = style
+ self.end = end
+ self.align = align
+
+ def __repr__(self) -> str:
+ return f"Rule({self.title!r}, {self.characters!r})"
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ width = options.max_width
+
+ # Python3.6 doesn't have an isascii method on str
+ isascii = getattr(str, "isascii", None) or (
+ lambda s: all(ord(c) < 128 for c in s)
+ )
+ characters = (
+ "-"
+ if (options.ascii_only and not isascii(self.characters))
+ else self.characters
+ )
+
+ chars_len = cell_len(characters)
+ if not self.title:
+ yield self._rule_line(chars_len, width)
+ return
+
+ if isinstance(self.title, Text):
+ title_text = self.title
+ else:
+ title_text = console.render_str(self.title, style="rule.text")
+
+ title_text.plain = title_text.plain.replace("\n", " ")
+ title_text.expand_tabs()
+
+ required_space = 4 if self.align == "center" else 2
+ truncate_width = max(0, width - required_space)
+ if not truncate_width:
+ yield self._rule_line(chars_len, width)
+ return
+
+ rule_text = Text(end=self.end)
+ if self.align == "center":
+ title_text.truncate(truncate_width, overflow="ellipsis")
+ side_width = (width - cell_len(title_text.plain)) // 2
+ left = Text(characters * (side_width // chars_len + 1))
+ left.truncate(side_width - 1)
+ right_length = width - cell_len(left.plain) - cell_len(title_text.plain)
+ right = Text(characters * (side_width // chars_len + 1))
+ right.truncate(right_length)
+ rule_text.append(left.plain + " ", self.style)
+ rule_text.append(title_text)
+ rule_text.append(" " + right.plain, self.style)
+ elif self.align == "left":
+ title_text.truncate(truncate_width, overflow="ellipsis")
+ rule_text.append(title_text)
+ rule_text.append(" ")
+ rule_text.append(characters * (width - rule_text.cell_len), self.style)
+ elif self.align == "right":
+ title_text.truncate(truncate_width, overflow="ellipsis")
+ rule_text.append(characters * (width - title_text.cell_len - 1), self.style)
+ rule_text.append(" ")
+ rule_text.append(title_text)
+
+ rule_text.plain = set_cell_size(rule_text.plain, width)
+ yield rule_text
+
+ def _rule_line(self, chars_len: int, width: int) -> Text:
+ rule_text = Text(self.characters * ((width // chars_len) + 1), self.style)
+ rule_text.truncate(width)
+ rule_text.plain = set_cell_size(rule_text.plain, width)
+ return rule_text
+
+ def __rich_measure__(
+ self, console: Console, options: ConsoleOptions
+ ) -> Measurement:
+ return Measurement(1, 1)
+
+
+if __name__ == "__main__": # pragma: no cover
+ import sys
+
+ from pip._vendor.rich.console import Console
+
+ try:
+ text = sys.argv[1]
+ except IndexError:
+ text = "Hello, World"
+ console = Console()
+ console.print(Rule(title=text))
+
+ console = Console()
+ console.print(Rule("foo"), width=4)
diff --git a/src/pip/_vendor/rich/scope.py b/src/pip/_vendor/rich/scope.py
new file mode 100644
index 000000000..c9d134cc3
--- /dev/null
+++ b/src/pip/_vendor/rich/scope.py
@@ -0,0 +1,86 @@
+from collections.abc import Mapping
+from typing import TYPE_CHECKING, Any, Optional, Tuple
+
+from .highlighter import ReprHighlighter
+from .panel import Panel
+from .pretty import Pretty
+from .table import Table
+from .text import Text, TextType
+
+if TYPE_CHECKING:
+ from .console import ConsoleRenderable
+
+
+def render_scope(
+ scope: "Mapping[str, Any]",
+ *,
+ title: Optional[TextType] = None,
+ sort_keys: bool = True,
+ indent_guides: bool = False,
+ max_length: Optional[int] = None,
+ max_string: Optional[int] = None,
+) -> "ConsoleRenderable":
+ """Render python variables in a given scope.
+
+ Args:
+ scope (Mapping): A mapping containing variable names and values.
+ title (str, optional): Optional title. Defaults to None.
+ sort_keys (bool, optional): Enable sorting of items. Defaults to True.
+ indent_guides (bool, optional): Enable indentation guides. Defaults to False.
+ max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
+ Defaults to None.
+ max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
+
+ Returns:
+ ConsoleRenderable: A renderable object.
+ """
+ highlighter = ReprHighlighter()
+ items_table = Table.grid(padding=(0, 1), expand=False)
+ items_table.add_column(justify="right")
+
+ def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]:
+ """Sort special variables first, then alphabetically."""
+ key, _ = item
+ return (not key.startswith("__"), key.lower())
+
+ items = sorted(scope.items(), key=sort_items) if sort_keys else scope.items()
+ for key, value in items:
+ key_text = Text.assemble(
+ (key, "scope.key.special" if key.startswith("__") else "scope.key"),
+ (" =", "scope.equals"),
+ )
+ items_table.add_row(
+ key_text,
+ Pretty(
+ value,
+ highlighter=highlighter,
+ indent_guides=indent_guides,
+ max_length=max_length,
+ max_string=max_string,
+ ),
+ )
+ return Panel.fit(
+ items_table,
+ title=title,
+ border_style="scope.border",
+ padding=(0, 1),
+ )
+
+
+if __name__ == "__main__": # pragma: no cover
+ from pip._vendor.rich import print
+
+ print()
+
+ def test(foo: float, bar: float) -> None:
+ list_of_things = [1, 2, 3, None, 4, True, False, "Hello World"]
+ dict_of_things = {
+ "version": "1.1",
+ "method": "confirmFruitPurchase",
+ "params": [["apple", "orange", "mangoes", "pomelo"], 1.123],
+ "id": "194521489",
+ }
+ print(render_scope(locals(), title="[i]locals", sort_keys=False))
+
+ test(20.3423, 3.1427)
+ print()
diff --git a/src/pip/_vendor/rich/screen.py b/src/pip/_vendor/rich/screen.py
new file mode 100644
index 000000000..7f416e1e7
--- /dev/null
+++ b/src/pip/_vendor/rich/screen.py
@@ -0,0 +1,54 @@
+from typing import Optional, TYPE_CHECKING
+
+from .segment import Segment
+from .style import StyleType
+from ._loop import loop_last
+
+
+if TYPE_CHECKING:
+ from .console import (
+ Console,
+ ConsoleOptions,
+ RenderResult,
+ RenderableType,
+ Group,
+ )
+
+
+class Screen:
+ """A renderable that fills the terminal screen and crops excess.
+
+ Args:
+ renderable (RenderableType): Child renderable.
+ style (StyleType, optional): Optional background style. Defaults to None.
+ """
+
+ renderable: "RenderableType"
+
+ def __init__(
+ self,
+ *renderables: "RenderableType",
+ style: Optional[StyleType] = None,
+ application_mode: bool = False,
+ ) -> None:
+ from pip._vendor.rich.console import Group
+
+ self.renderable = Group(*renderables)
+ self.style = style
+ self.application_mode = application_mode
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ width, height = options.size
+ style = console.get_style(self.style) if self.style else None
+ render_options = options.update(width=width, height=height)
+ lines = console.render_lines(
+ self.renderable or "", render_options, style=style, pad=True
+ )
+ lines = Segment.set_shape(lines, width, height, style=style)
+ new_line = Segment("\n\r") if self.application_mode else Segment.line()
+ for last, line in loop_last(lines):
+ yield from line
+ if not last:
+ yield new_line
diff --git a/src/pip/_vendor/rich/segment.py b/src/pip/_vendor/rich/segment.py
new file mode 100644
index 000000000..1ea5435ad
--- /dev/null
+++ b/src/pip/_vendor/rich/segment.py
@@ -0,0 +1,739 @@
+from enum import IntEnum
+from functools import lru_cache
+from itertools import filterfalse
+from logging import getLogger
+from operator import attrgetter
+from typing import (
+ TYPE_CHECKING,
+ Dict,
+ Iterable,
+ List,
+ NamedTuple,
+ Optional,
+ Sequence,
+ Tuple,
+ Type,
+ Union,
+)
+
+from .cells import (
+ _is_single_cell_widths,
+ cached_cell_len,
+ cell_len,
+ get_character_cell_size,
+ set_cell_size,
+)
+from .repr import Result, rich_repr
+from .style import Style
+
+if TYPE_CHECKING:
+ from .console import Console, ConsoleOptions, RenderResult
+
+log = getLogger("rich")
+
+
+class ControlType(IntEnum):
+ """Non-printable control codes which typically translate to ANSI codes."""
+
+ BELL = 1
+ CARRIAGE_RETURN = 2
+ HOME = 3
+ CLEAR = 4
+ SHOW_CURSOR = 5
+ HIDE_CURSOR = 6
+ ENABLE_ALT_SCREEN = 7
+ DISABLE_ALT_SCREEN = 8
+ CURSOR_UP = 9
+ CURSOR_DOWN = 10
+ CURSOR_FORWARD = 11
+ CURSOR_BACKWARD = 12
+ CURSOR_MOVE_TO_COLUMN = 13
+ CURSOR_MOVE_TO = 14
+ ERASE_IN_LINE = 15
+ SET_WINDOW_TITLE = 16
+
+
+ControlCode = Union[
+ Tuple[ControlType],
+ Tuple[ControlType, Union[int, str]],
+ Tuple[ControlType, int, int],
+]
+
+
+@rich_repr()
+class Segment(NamedTuple):
+ """A piece of text with associated style. Segments are produced by the Console render process and
+ are ultimately converted in to strings to be written to the terminal.
+
+ Args:
+ text (str): A piece of text.
+ style (:class:`~rich.style.Style`, optional): An optional style to apply to the text.
+ control (Tuple[ControlCode], optional): Optional sequence of control codes.
+
+ Attributes:
+ cell_length (int): The cell length of this Segment.
+ """
+
+ text: str
+ style: Optional[Style] = None
+ control: Optional[Sequence[ControlCode]] = None
+
+ @property
+ def cell_length(self) -> int:
+ """The number of terminal cells required to display self.text.
+
+ Returns:
+ int: A number of cells.
+ """
+ text, _style, control = self
+ return 0 if control else cell_len(text)
+
+ def __rich_repr__(self) -> Result:
+ yield self.text
+ if self.control is None:
+ if self.style is not None:
+ yield self.style
+ else:
+ yield self.style
+ yield self.control
+
+ def __bool__(self) -> bool:
+ """Check if the segment contains text."""
+ return bool(self.text)
+
+ @property
+ def is_control(self) -> bool:
+ """Check if the segment contains control codes."""
+ return self.control is not None
+
+ @classmethod
+ @lru_cache(1024 * 16)
+ def _split_cells(cls, segment: "Segment", cut: int) -> Tuple["Segment", "Segment"]:
+
+ text, style, control = segment
+ _Segment = Segment
+
+ cell_length = segment.cell_length
+ if cut >= cell_length:
+ return segment, _Segment("", style, control)
+
+ cell_size = get_character_cell_size
+
+ pos = int((cut / cell_length) * len(text))
+
+ before = text[:pos]
+ cell_pos = cell_len(before)
+ if cell_pos == cut:
+ return (
+ _Segment(before, style, control),
+ _Segment(text[pos:], style, control),
+ )
+ while pos < len(text):
+ char = text[pos]
+ pos += 1
+ cell_pos += cell_size(char)
+ before = text[:pos]
+ if cell_pos == cut:
+ return (
+ _Segment(before, style, control),
+ _Segment(text[pos:], style, control),
+ )
+ if cell_pos > cut:
+ return (
+ _Segment(before[: pos - 1] + " ", style, control),
+ _Segment(" " + text[pos:], style, control),
+ )
+
+ raise AssertionError("Will never reach here")
+
+ def split_cells(self, cut: int) -> Tuple["Segment", "Segment"]:
+ """Split segment in to two segments at the specified column.
+
+ If the cut point falls in the middle of a 2-cell wide character then it is replaced
+ by two spaces, to preserve the display width of the parent segment.
+
+ Returns:
+ Tuple[Segment, Segment]: Two segments.
+ """
+ text, style, control = self
+
+ if _is_single_cell_widths(text):
+ # Fast path with all 1 cell characters
+ if cut >= len(text):
+ return self, Segment("", style, control)
+ return (
+ Segment(text[:cut], style, control),
+ Segment(text[cut:], style, control),
+ )
+
+ return self._split_cells(self, cut)
+
+ @classmethod
+ def line(cls) -> "Segment":
+ """Make a new line segment."""
+ return cls("\n")
+
+ @classmethod
+ def apply_style(
+ cls,
+ segments: Iterable["Segment"],
+ style: Optional[Style] = None,
+ post_style: Optional[Style] = None,
+ ) -> Iterable["Segment"]:
+ """Apply style(s) to an iterable of segments.
+
+ Returns an iterable of segments where the style is replaced by ``style + segment.style + post_style``.
+
+ Args:
+ segments (Iterable[Segment]): Segments to process.
+ style (Style, optional): Base style. Defaults to None.
+ post_style (Style, optional): Style to apply on top of segment style. Defaults to None.
+
+ Returns:
+ Iterable[Segments]: A new iterable of segments (possibly the same iterable).
+ """
+ result_segments = segments
+ if style:
+ apply = style.__add__
+ result_segments = (
+ cls(text, None if control else apply(_style), control)
+ for text, _style, control in result_segments
+ )
+ if post_style:
+ result_segments = (
+ cls(
+ text,
+ (
+ None
+ if control
+ else (_style + post_style if _style else post_style)
+ ),
+ control,
+ )
+ for text, _style, control in result_segments
+ )
+ return result_segments
+
+ @classmethod
+ def filter_control(
+ cls, segments: Iterable["Segment"], is_control: bool = False
+ ) -> Iterable["Segment"]:
+ """Filter segments by ``is_control`` attribute.
+
+ Args:
+ segments (Iterable[Segment]): An iterable of Segment instances.
+ is_control (bool, optional): is_control flag to match in search.
+
+ Returns:
+ Iterable[Segment]: And iterable of Segment instances.
+
+ """
+ if is_control:
+ return filter(attrgetter("control"), segments)
+ else:
+ return filterfalse(attrgetter("control"), segments)
+
+ @classmethod
+ def split_lines(cls, segments: Iterable["Segment"]) -> Iterable[List["Segment"]]:
+ """Split a sequence of segments in to a list of lines.
+
+ Args:
+ segments (Iterable[Segment]): Segments potentially containing line feeds.
+
+ Yields:
+ Iterable[List[Segment]]: Iterable of segment lists, one per line.
+ """
+ line: List[Segment] = []
+ append = line.append
+
+ for segment in segments:
+ if "\n" in segment.text and not segment.control:
+ text, style, _ = segment
+ while text:
+ _text, new_line, text = text.partition("\n")
+ if _text:
+ append(cls(_text, style))
+ if new_line:
+ yield line
+ line = []
+ append = line.append
+ else:
+ append(segment)
+ if line:
+ yield line
+
+ @classmethod
+ def split_and_crop_lines(
+ cls,
+ segments: Iterable["Segment"],
+ length: int,
+ style: Optional[Style] = None,
+ pad: bool = True,
+ include_new_lines: bool = True,
+ ) -> Iterable[List["Segment"]]:
+ """Split segments in to lines, and crop lines greater than a given length.
+
+ Args:
+ segments (Iterable[Segment]): An iterable of segments, probably
+ generated from console.render.
+ length (int): Desired line length.
+ style (Style, optional): Style to use for any padding.
+ pad (bool): Enable padding of lines that are less than `length`.
+
+ Returns:
+ Iterable[List[Segment]]: An iterable of lines of segments.
+ """
+ line: List[Segment] = []
+ append = line.append
+
+ adjust_line_length = cls.adjust_line_length
+ new_line_segment = cls("\n")
+
+ for segment in segments:
+ if "\n" in segment.text and not segment.control:
+ text, segment_style, _ = segment
+ while text:
+ _text, new_line, text = text.partition("\n")
+ if _text:
+ append(cls(_text, segment_style))
+ if new_line:
+ cropped_line = adjust_line_length(
+ line, length, style=style, pad=pad
+ )
+ if include_new_lines:
+ cropped_line.append(new_line_segment)
+ yield cropped_line
+ del line[:]
+ else:
+ append(segment)
+ if line:
+ yield adjust_line_length(line, length, style=style, pad=pad)
+
+ @classmethod
+ def adjust_line_length(
+ cls,
+ line: List["Segment"],
+ length: int,
+ style: Optional[Style] = None,
+ pad: bool = True,
+ ) -> List["Segment"]:
+ """Adjust a line to a given width (cropping or padding as required).
+
+ Args:
+ segments (Iterable[Segment]): A list of segments in a single line.
+ length (int): The desired width of the line.
+ style (Style, optional): The style of padding if used (space on the end). Defaults to None.
+ pad (bool, optional): Pad lines with spaces if they are shorter than `length`. Defaults to True.
+
+ Returns:
+ List[Segment]: A line of segments with the desired length.
+ """
+ line_length = sum(segment.cell_length for segment in line)
+ new_line: List[Segment]
+
+ if line_length < length:
+ if pad:
+ new_line = line + [cls(" " * (length - line_length), style)]
+ else:
+ new_line = line[:]
+ elif line_length > length:
+ new_line = []
+ append = new_line.append
+ line_length = 0
+ for segment in line:
+ segment_length = segment.cell_length
+ if line_length + segment_length < length or segment.control:
+ append(segment)
+ line_length += segment_length
+ else:
+ text, segment_style, _ = segment
+ text = set_cell_size(text, length - line_length)
+ append(cls(text, segment_style))
+ break
+ else:
+ new_line = line[:]
+ return new_line
+
+ @classmethod
+ def get_line_length(cls, line: List["Segment"]) -> int:
+ """Get the length of list of segments.
+
+ Args:
+ line (List[Segment]): A line encoded as a list of Segments (assumes no '\\\\n' characters),
+
+ Returns:
+ int: The length of the line.
+ """
+ _cell_len = cell_len
+ return sum(_cell_len(segment.text) for segment in line)
+
+ @classmethod
+ def get_shape(cls, lines: List[List["Segment"]]) -> Tuple[int, int]:
+ """Get the shape (enclosing rectangle) of a list of lines.
+
+ Args:
+ lines (List[List[Segment]]): A list of lines (no '\\\\n' characters).
+
+ Returns:
+ Tuple[int, int]: Width and height in characters.
+ """
+ get_line_length = cls.get_line_length
+ max_width = max(get_line_length(line) for line in lines) if lines else 0
+ return (max_width, len(lines))
+
+ @classmethod
+ def set_shape(
+ cls,
+ lines: List[List["Segment"]],
+ width: int,
+ height: Optional[int] = None,
+ style: Optional[Style] = None,
+ new_lines: bool = False,
+ ) -> List[List["Segment"]]:
+ """Set the shape of a list of lines (enclosing rectangle).
+
+ Args:
+ lines (List[List[Segment]]): A list of lines.
+ width (int): Desired width.
+ height (int, optional): Desired height or None for no change.
+ style (Style, optional): Style of any padding added.
+ new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
+
+ Returns:
+ List[List[Segment]]: New list of lines.
+ """
+ _height = height or len(lines)
+
+ blank = (
+ [cls(" " * width + "\n", style)] if new_lines else [cls(" " * width, style)]
+ )
+
+ adjust_line_length = cls.adjust_line_length
+ shaped_lines = lines[:_height]
+ shaped_lines[:] = [
+ adjust_line_length(line, width, style=style) for line in lines
+ ]
+ if len(shaped_lines) < _height:
+ shaped_lines.extend([blank] * (_height - len(shaped_lines)))
+ return shaped_lines
+
+ @classmethod
+ def align_top(
+ cls: Type["Segment"],
+ lines: List[List["Segment"]],
+ width: int,
+ height: int,
+ style: Style,
+ new_lines: bool = False,
+ ) -> List[List["Segment"]]:
+ """Aligns lines to top (adds extra lines to bottom as required).
+
+ Args:
+ lines (List[List[Segment]]): A list of lines.
+ width (int): Desired width.
+ height (int, optional): Desired height or None for no change.
+ style (Style): Style of any padding added.
+ new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
+
+ Returns:
+ List[List[Segment]]: New list of lines.
+ """
+ extra_lines = height - len(lines)
+ if not extra_lines:
+ return lines[:]
+ lines = lines[:height]
+ blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style)
+ lines = lines + [[blank]] * extra_lines
+ return lines
+
+ @classmethod
+ def align_bottom(
+ cls: Type["Segment"],
+ lines: List[List["Segment"]],
+ width: int,
+ height: int,
+ style: Style,
+ new_lines: bool = False,
+ ) -> List[List["Segment"]]:
+ """Aligns render to bottom (adds extra lines above as required).
+
+ Args:
+ lines (List[List[Segment]]): A list of lines.
+ width (int): Desired width.
+ height (int, optional): Desired height or None for no change.
+ style (Style): Style of any padding added. Defaults to None.
+ new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
+
+ Returns:
+ List[List[Segment]]: New list of lines.
+ """
+ extra_lines = height - len(lines)
+ if not extra_lines:
+ return lines[:]
+ lines = lines[:height]
+ blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style)
+ lines = [[blank]] * extra_lines + lines
+ return lines
+
+ @classmethod
+ def align_middle(
+ cls: Type["Segment"],
+ lines: List[List["Segment"]],
+ width: int,
+ height: int,
+ style: Style,
+ new_lines: bool = False,
+ ) -> List[List["Segment"]]:
+ """Aligns lines to middle (adds extra lines to above and below as required).
+
+ Args:
+ lines (List[List[Segment]]): A list of lines.
+ width (int): Desired width.
+ height (int, optional): Desired height or None for no change.
+ style (Style): Style of any padding added.
+ new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
+
+ Returns:
+ List[List[Segment]]: New list of lines.
+ """
+ extra_lines = height - len(lines)
+ if not extra_lines:
+ return lines[:]
+ lines = lines[:height]
+ blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style)
+ top_lines = extra_lines // 2
+ bottom_lines = extra_lines - top_lines
+ lines = [[blank]] * top_lines + lines + [[blank]] * bottom_lines
+ return lines
+
+ @classmethod
+ def simplify(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
+ """Simplify an iterable of segments by combining contiguous segments with the same style.
+
+ Args:
+ segments (Iterable[Segment]): An iterable of segments.
+
+ Returns:
+ Iterable[Segment]: A possibly smaller iterable of segments that will render the same way.
+ """
+ iter_segments = iter(segments)
+ try:
+ last_segment = next(iter_segments)
+ except StopIteration:
+ return
+
+ _Segment = Segment
+ for segment in iter_segments:
+ if last_segment.style == segment.style and not segment.control:
+ last_segment = _Segment(
+ last_segment.text + segment.text, last_segment.style
+ )
+ else:
+ yield last_segment
+ last_segment = segment
+ yield last_segment
+
+ @classmethod
+ def strip_links(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
+ """Remove all links from an iterable of styles.
+
+ Args:
+ segments (Iterable[Segment]): An iterable segments.
+
+ Yields:
+ Segment: Segments with link removed.
+ """
+ for segment in segments:
+ if segment.control or segment.style is None:
+ yield segment
+ else:
+ text, style, _control = segment
+ yield cls(text, style.update_link(None) if style else None)
+
+ @classmethod
+ def strip_styles(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
+ """Remove all styles from an iterable of segments.
+
+ Args:
+ segments (Iterable[Segment]): An iterable segments.
+
+ Yields:
+ Segment: Segments with styles replace with None
+ """
+ for text, _style, control in segments:
+ yield cls(text, None, control)
+
+ @classmethod
+ def remove_color(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
+ """Remove all color from an iterable of segments.
+
+ Args:
+ segments (Iterable[Segment]): An iterable segments.
+
+ Yields:
+ Segment: Segments with colorless style.
+ """
+
+ cache: Dict[Style, Style] = {}
+ for text, style, control in segments:
+ if style:
+ colorless_style = cache.get(style)
+ if colorless_style is None:
+ colorless_style = style.without_color
+ cache[style] = colorless_style
+ yield cls(text, colorless_style, control)
+ else:
+ yield cls(text, None, control)
+
+ @classmethod
+ def divide(
+ cls, segments: Iterable["Segment"], cuts: Iterable[int]
+ ) -> Iterable[List["Segment"]]:
+ """Divides an iterable of segments in to portions.
+
+ Args:
+ cuts (Iterable[int]): Cell positions where to divide.
+
+ Yields:
+ [Iterable[List[Segment]]]: An iterable of Segments in List.
+ """
+ split_segments: List["Segment"] = []
+ add_segment = split_segments.append
+
+ iter_cuts = iter(cuts)
+
+ while True:
+ cut = next(iter_cuts, -1)
+ if cut == -1:
+ return []
+ if cut != 0:
+ break
+ yield []
+ pos = 0
+
+ segments_clear = split_segments.clear
+ segments_copy = split_segments.copy
+
+ _cell_len = cached_cell_len
+ for segment in segments:
+ text, _style, control = segment
+ while text:
+ end_pos = pos if control else pos + _cell_len(text)
+ if end_pos < cut:
+ add_segment(segment)
+ pos = end_pos
+ break
+
+ if end_pos == cut:
+ add_segment(segment)
+ yield segments_copy()
+ segments_clear()
+ pos = end_pos
+
+ cut = next(iter_cuts, -1)
+ if cut == -1:
+ if split_segments:
+ yield segments_copy()
+ return
+
+ break
+
+ else:
+ before, segment = segment.split_cells(cut - pos)
+ text, _style, control = segment
+ add_segment(before)
+ yield segments_copy()
+ segments_clear()
+ pos = cut
+
+ cut = next(iter_cuts, -1)
+ if cut == -1:
+ if split_segments:
+ yield segments_copy()
+ return
+
+ yield segments_copy()
+
+
+class Segments:
+ """A simple renderable to render an iterable of segments. This class may be useful if
+ you want to print segments outside of a __rich_console__ method.
+
+ Args:
+ segments (Iterable[Segment]): An iterable of segments.
+ new_lines (bool, optional): Add new lines between segments. Defaults to False.
+ """
+
+ def __init__(self, segments: Iterable[Segment], new_lines: bool = False) -> None:
+ self.segments = list(segments)
+ self.new_lines = new_lines
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ if self.new_lines:
+ line = Segment.line()
+ for segment in self.segments:
+ yield segment
+ yield line
+ else:
+ yield from self.segments
+
+
+class SegmentLines:
+ def __init__(self, lines: Iterable[List[Segment]], new_lines: bool = False) -> None:
+ """A simple renderable containing a number of lines of segments. May be used as an intermediate
+ in rendering process.
+
+ Args:
+ lines (Iterable[List[Segment]]): Lists of segments forming lines.
+ new_lines (bool, optional): Insert new lines after each line. Defaults to False.
+ """
+ self.lines = list(lines)
+ self.new_lines = new_lines
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ if self.new_lines:
+ new_line = Segment.line()
+ for line in self.lines:
+ yield from line
+ yield new_line
+ else:
+ for line in self.lines:
+ yield from line
+
+
+if __name__ == "__main__": # pragma: no cover
+ from pip._vendor.rich.console import Console
+ from pip._vendor.rich.syntax import Syntax
+ from pip._vendor.rich.text import Text
+
+ code = """from rich.console import Console
+console = Console()
+text = Text.from_markup("Hello, [bold magenta]World[/]!")
+console.print(text)"""
+
+ text = Text.from_markup("Hello, [bold magenta]World[/]!")
+
+ console = Console()
+
+ console.rule("rich.Segment")
+ console.print(
+ "A Segment is the last step in the Rich render process before generating text with ANSI codes."
+ )
+ console.print("\nConsider the following code:\n")
+ console.print(Syntax(code, "python", line_numbers=True))
+ console.print()
+ console.print(
+ "When you call [b]print()[/b], Rich [i]renders[/i] the object in to the the following:\n"
+ )
+ fragments = list(console.render(text))
+ console.print(fragments)
+ console.print()
+ console.print("The Segments are then processed to produce the following output:\n")
+ console.print(text)
+ console.print(
+ "\nYou will only need to know this if you are implementing your own Rich renderables."
+ )
diff --git a/src/pip/_vendor/rich/spinner.py b/src/pip/_vendor/rich/spinner.py
new file mode 100644
index 000000000..0879088e1
--- /dev/null
+++ b/src/pip/_vendor/rich/spinner.py
@@ -0,0 +1,136 @@
+from typing import cast, List, Optional, TYPE_CHECKING, Union
+
+from ._spinners import SPINNERS
+from .measure import Measurement
+from .table import Table
+from .text import Text
+
+if TYPE_CHECKING:
+ from .console import Console, ConsoleOptions, RenderResult, RenderableType
+ from .style import StyleType
+
+
+class Spinner:
+ def __init__(
+ self,
+ name: str,
+ text: "RenderableType" = "",
+ *,
+ style: Optional["StyleType"] = None,
+ speed: float = 1.0,
+ ) -> None:
+ """A spinner animation.
+
+ Args:
+ name (str): Name of spinner (run python -m rich.spinner).
+ text (RenderableType, optional): A renderable to display at the right of the spinner (str or Text typically). Defaults to "".
+ style (StyleType, optional): Style for spinner animation. Defaults to None.
+ speed (float, optional): Speed factor for animation. Defaults to 1.0.
+
+ Raises:
+ KeyError: If name isn't one of the supported spinner animations.
+ """
+ try:
+ spinner = SPINNERS[name]
+ except KeyError:
+ raise KeyError(f"no spinner called {name!r}")
+ self.text: "Union[RenderableType, Text]" = (
+ Text.from_markup(text) if isinstance(text, str) else text
+ )
+ self.frames = cast(List[str], spinner["frames"])[:]
+ self.interval = cast(float, spinner["interval"])
+ self.start_time: Optional[float] = None
+ self.style = style
+ self.speed = speed
+ self.frame_no_offset: float = 0.0
+ self._update_speed = 0.0
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ yield self.render(console.get_time())
+
+ def __rich_measure__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> Measurement:
+ text = self.render(0)
+ return Measurement.get(console, options, text)
+
+ def render(self, time: float) -> "RenderableType":
+ """Render the spinner for a given time.
+
+ Args:
+ time (float): Time in seconds.
+
+ Returns:
+ RenderableType: A renderable containing animation frame.
+ """
+ if self.start_time is None:
+ self.start_time = time
+
+ frame_no = ((time - self.start_time) * self.speed) / (
+ self.interval / 1000.0
+ ) + self.frame_no_offset
+ frame = Text(
+ self.frames[int(frame_no) % len(self.frames)], style=self.style or ""
+ )
+
+ if self._update_speed:
+ self.frame_no_offset = frame_no
+ self.start_time = time
+ self.speed = self._update_speed
+ self._update_speed = 0.0
+
+ if not self.text:
+ return frame
+ elif isinstance(self.text, (str, Text)):
+ return Text.assemble(frame, " ", self.text)
+ else:
+ table = Table.grid(padding=1)
+ table.add_row(frame, self.text)
+ return table
+
+ def update(
+ self,
+ *,
+ text: "RenderableType" = "",
+ style: Optional["StyleType"] = None,
+ speed: Optional[float] = None,
+ ) -> None:
+ """Updates attributes of a spinner after it has been started.
+
+ Args:
+ text (RenderableType, optional): A renderable to display at the right of the spinner (str or Text typically). Defaults to "".
+ style (StyleType, optional): Style for spinner animation. Defaults to None.
+ speed (float, optional): Speed factor for animation. Defaults to None.
+ """
+ if text:
+ self.text = Text.from_markup(text) if isinstance(text, str) else text
+ if style:
+ self.style = style
+ if speed:
+ self._update_speed = speed
+
+
+if __name__ == "__main__": # pragma: no cover
+ from time import sleep
+
+ from .columns import Columns
+ from .panel import Panel
+ from .live import Live
+
+ all_spinners = Columns(
+ [
+ Spinner(spinner_name, text=Text(repr(spinner_name), style="green"))
+ for spinner_name in sorted(SPINNERS.keys())
+ ],
+ column_first=True,
+ expand=True,
+ )
+
+ with Live(
+ Panel(all_spinners, title="Spinners", border_style="blue"),
+ refresh_per_second=20,
+ ) as live:
+ while True:
+ sleep(0.1)
diff --git a/src/pip/_vendor/rich/status.py b/src/pip/_vendor/rich/status.py
new file mode 100644
index 000000000..09eff405e
--- /dev/null
+++ b/src/pip/_vendor/rich/status.py
@@ -0,0 +1,132 @@
+from types import TracebackType
+from typing import Optional, Type
+
+from .console import Console, RenderableType
+from .jupyter import JupyterMixin
+from .live import Live
+from .spinner import Spinner
+from .style import StyleType
+
+
+class Status(JupyterMixin):
+ """Displays a status indicator with a 'spinner' animation.
+
+ Args:
+ status (RenderableType): A status renderable (str or Text typically).
+ console (Console, optional): Console instance to use, or None for global console. Defaults to None.
+ spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots".
+ spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner".
+ speed (float, optional): Speed factor for spinner animation. Defaults to 1.0.
+ refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5.
+ """
+
+ def __init__(
+ self,
+ status: RenderableType,
+ *,
+ console: Optional[Console] = None,
+ spinner: str = "dots",
+ spinner_style: StyleType = "status.spinner",
+ speed: float = 1.0,
+ refresh_per_second: float = 12.5,
+ ):
+ self.status = status
+ self.spinner_style = spinner_style
+ self.speed = speed
+ self._spinner = Spinner(spinner, text=status, style=spinner_style, speed=speed)
+ self._live = Live(
+ self.renderable,
+ console=console,
+ refresh_per_second=refresh_per_second,
+ transient=True,
+ )
+
+ @property
+ def renderable(self) -> Spinner:
+ return self._spinner
+
+ @property
+ def console(self) -> "Console":
+ """Get the Console used by the Status objects."""
+ return self._live.console
+
+ def update(
+ self,
+ status: Optional[RenderableType] = None,
+ *,
+ spinner: Optional[str] = None,
+ spinner_style: Optional[StyleType] = None,
+ speed: Optional[float] = None,
+ ) -> None:
+ """Update status.
+
+ Args:
+ status (Optional[RenderableType], optional): New status renderable or None for no change. Defaults to None.
+ spinner (Optional[str], optional): New spinner or None for no change. Defaults to None.
+ spinner_style (Optional[StyleType], optional): New spinner style or None for no change. Defaults to None.
+ speed (Optional[float], optional): Speed factor for spinner animation or None for no change. Defaults to None.
+ """
+ if status is not None:
+ self.status = status
+ if spinner_style is not None:
+ self.spinner_style = spinner_style
+ if speed is not None:
+ self.speed = speed
+ if spinner is not None:
+ self._spinner = Spinner(
+ spinner, text=self.status, style=self.spinner_style, speed=self.speed
+ )
+ self._live.update(self.renderable, refresh=True)
+ else:
+ self._spinner.update(
+ text=self.status, style=self.spinner_style, speed=self.speed
+ )
+
+ def start(self) -> None:
+ """Start the status animation."""
+ self._live.start()
+
+ def stop(self) -> None:
+ """Stop the spinner animation."""
+ self._live.stop()
+
+ def __rich__(self) -> RenderableType:
+ return self.renderable
+
+ def __enter__(self) -> "Status":
+ self.start()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ self.stop()
+
+
+if __name__ == "__main__": # pragma: no cover
+
+ from time import sleep
+
+ from .console import Console
+
+ console = Console()
+ with console.status("[magenta]Covid detector booting up") as status:
+ sleep(3)
+ console.log("Importing advanced AI")
+ sleep(3)
+ console.log("Advanced Covid AI Ready")
+ sleep(3)
+ status.update(status="[bold blue] Scanning for Covid", spinner="earth")
+ sleep(3)
+ console.log("Found 10,000,000,000 copies of Covid32.exe")
+ sleep(3)
+ status.update(
+ status="[bold red]Moving Covid32.exe to Trash",
+ spinner="bouncingBall",
+ spinner_style="yellow",
+ )
+ sleep(5)
+ console.print("[bold green]Covid deleted successfully")
diff --git a/src/pip/_vendor/rich/style.py b/src/pip/_vendor/rich/style.py
new file mode 100644
index 000000000..ad388aadb
--- /dev/null
+++ b/src/pip/_vendor/rich/style.py
@@ -0,0 +1,773 @@
+import sys
+from functools import lru_cache
+from marshal import dumps, loads
+from random import randint
+from typing import Any, Dict, Iterable, List, Optional, Type, Union, cast
+
+from . import errors
+from .color import Color, ColorParseError, ColorSystem, blend_rgb
+from .repr import Result, rich_repr
+from .terminal_theme import DEFAULT_TERMINAL_THEME, TerminalTheme
+
+# Style instances and style definitions are often interchangeable
+StyleType = Union[str, "Style"]
+
+
+class _Bit:
+ """A descriptor to get/set a style attribute bit."""
+
+ __slots__ = ["bit"]
+
+ def __init__(self, bit_no: int) -> None:
+ self.bit = 1 << bit_no
+
+ def __get__(self, obj: "Style", objtype: Type["Style"]) -> Optional[bool]:
+ if obj._set_attributes & self.bit:
+ return obj._attributes & self.bit != 0
+ return None
+
+
+@rich_repr
+class Style:
+ """A terminal style.
+
+ A terminal style consists of a color (`color`), a background color (`bgcolor`), and a number of attributes, such
+ as bold, italic etc. The attributes have 3 states: they can either be on
+ (``True``), off (``False``), or not set (``None``).
+
+ Args:
+ color (Union[Color, str], optional): Color of terminal text. Defaults to None.
+ bgcolor (Union[Color, str], optional): Color of terminal background. Defaults to None.
+ bold (bool, optional): Enable bold text. Defaults to None.
+ dim (bool, optional): Enable dim text. Defaults to None.
+ italic (bool, optional): Enable italic text. Defaults to None.
+ underline (bool, optional): Enable underlined text. Defaults to None.
+ blink (bool, optional): Enabled blinking text. Defaults to None.
+ blink2 (bool, optional): Enable fast blinking text. Defaults to None.
+ reverse (bool, optional): Enabled reverse text. Defaults to None.
+ conceal (bool, optional): Enable concealed text. Defaults to None.
+ strike (bool, optional): Enable strikethrough text. Defaults to None.
+ underline2 (bool, optional): Enable doubly underlined text. Defaults to None.
+ frame (bool, optional): Enable framed text. Defaults to None.
+ encircle (bool, optional): Enable encircled text. Defaults to None.
+ overline (bool, optional): Enable overlined text. Defaults to None.
+ link (str, link): Link URL. Defaults to None.
+
+ """
+
+ _color: Optional[Color]
+ _bgcolor: Optional[Color]
+ _attributes: int
+ _set_attributes: int
+ _hash: Optional[int]
+ _null: bool
+ _meta: Optional[bytes]
+
+ __slots__ = [
+ "_color",
+ "_bgcolor",
+ "_attributes",
+ "_set_attributes",
+ "_link",
+ "_link_id",
+ "_ansi",
+ "_style_definition",
+ "_hash",
+ "_null",
+ "_meta",
+ ]
+
+ # maps bits on to SGR parameter
+ _style_map = {
+ 0: "1",
+ 1: "2",
+ 2: "3",
+ 3: "4",
+ 4: "5",
+ 5: "6",
+ 6: "7",
+ 7: "8",
+ 8: "9",
+ 9: "21",
+ 10: "51",
+ 11: "52",
+ 12: "53",
+ }
+
+ STYLE_ATTRIBUTES = {
+ "dim": "dim",
+ "d": "dim",
+ "bold": "bold",
+ "b": "bold",
+ "italic": "italic",
+ "i": "italic",
+ "underline": "underline",
+ "u": "underline",
+ "blink": "blink",
+ "blink2": "blink2",
+ "reverse": "reverse",
+ "r": "reverse",
+ "conceal": "conceal",
+ "c": "conceal",
+ "strike": "strike",
+ "s": "strike",
+ "underline2": "underline2",
+ "uu": "underline2",
+ "frame": "frame",
+ "encircle": "encircle",
+ "overline": "overline",
+ "o": "overline",
+ }
+
+ def __init__(
+ self,
+ *,
+ color: Optional[Union[Color, str]] = None,
+ bgcolor: Optional[Union[Color, str]] = None,
+ bold: Optional[bool] = None,
+ dim: Optional[bool] = None,
+ italic: Optional[bool] = None,
+ underline: Optional[bool] = None,
+ blink: Optional[bool] = None,
+ blink2: Optional[bool] = None,
+ reverse: Optional[bool] = None,
+ conceal: Optional[bool] = None,
+ strike: Optional[bool] = None,
+ underline2: Optional[bool] = None,
+ frame: Optional[bool] = None,
+ encircle: Optional[bool] = None,
+ overline: Optional[bool] = None,
+ link: Optional[str] = None,
+ meta: Optional[Dict[str, Any]] = None,
+ ):
+ self._ansi: Optional[str] = None
+ self._style_definition: Optional[str] = None
+
+ def _make_color(color: Union[Color, str]) -> Color:
+ return color if isinstance(color, Color) else Color.parse(color)
+
+ self._color = None if color is None else _make_color(color)
+ self._bgcolor = None if bgcolor is None else _make_color(bgcolor)
+ self._set_attributes = sum(
+ (
+ bold is not None,
+ dim is not None and 2,
+ italic is not None and 4,
+ underline is not None and 8,
+ blink is not None and 16,
+ blink2 is not None and 32,
+ reverse is not None and 64,
+ conceal is not None and 128,
+ strike is not None and 256,
+ underline2 is not None and 512,
+ frame is not None and 1024,
+ encircle is not None and 2048,
+ overline is not None and 4096,
+ )
+ )
+ self._attributes = (
+ sum(
+ (
+ bold and 1 or 0,
+ dim and 2 or 0,
+ italic and 4 or 0,
+ underline and 8 or 0,
+ blink and 16 or 0,
+ blink2 and 32 or 0,
+ reverse and 64 or 0,
+ conceal and 128 or 0,
+ strike and 256 or 0,
+ underline2 and 512 or 0,
+ frame and 1024 or 0,
+ encircle and 2048 or 0,
+ overline and 4096 or 0,
+ )
+ )
+ if self._set_attributes
+ else 0
+ )
+
+ self._link = link
+ self._meta = None if meta is None else dumps(meta)
+ self._link_id = (
+ f"{randint(0, 999999)}{hash(self._meta)}" if (link or meta) else ""
+ )
+ self._hash: Optional[int] = None
+ self._null = not (self._set_attributes or color or bgcolor or link or meta)
+
+ @classmethod
+ def null(cls) -> "Style":
+ """Create an 'null' style, equivalent to Style(), but more performant."""
+ return NULL_STYLE
+
+ @classmethod
+ def from_color(
+ cls, color: Optional[Color] = None, bgcolor: Optional[Color] = None
+ ) -> "Style":
+ """Create a new style with colors and no attributes.
+
+ Returns:
+ color (Optional[Color]): A (foreground) color, or None for no color. Defaults to None.
+ bgcolor (Optional[Color]): A (background) color, or None for no color. Defaults to None.
+ """
+ style: Style = cls.__new__(Style)
+ style._ansi = None
+ style._style_definition = None
+ style._color = color
+ style._bgcolor = bgcolor
+ style._set_attributes = 0
+ style._attributes = 0
+ style._link = None
+ style._link_id = ""
+ style._meta = None
+ style._null = not (color or bgcolor)
+ style._hash = None
+ return style
+
+ @classmethod
+ def from_meta(cls, meta: Optional[Dict[str, Any]]) -> "Style":
+ """Create a new style with meta data.
+
+ Returns:
+ meta (Optional[Dict[str, Any]]): A dictionary of meta data. Defaults to None.
+ """
+ style: Style = cls.__new__(Style)
+ style._ansi = None
+ style._style_definition = None
+ style._color = None
+ style._bgcolor = None
+ style._set_attributes = 0
+ style._attributes = 0
+ style._link = None
+ style._meta = dumps(meta)
+ style._link_id = f"{randint(0, 999999)}{hash(style._meta)}"
+ style._hash = None
+ style._null = not (meta)
+ return style
+
+ @classmethod
+ def on(cls, meta: Optional[Dict[str, Any]] = None, **handlers: Any) -> "Style":
+ """Create a blank style with meta information.
+
+ Example:
+ style = Style.on(click=self.on_click)
+
+ Args:
+ meta (Optional[Dict[str, Any]], optional): An optional dict of meta information.
+ **handlers (Any): Keyword arguments are translated in to handlers.
+
+ Returns:
+ Style: A Style with meta information attached.
+ """
+ meta = {} if meta is None else meta
+ meta.update({f"@{key}": value for key, value in handlers.items()})
+ return cls.from_meta(meta)
+
+ bold = _Bit(0)
+ dim = _Bit(1)
+ italic = _Bit(2)
+ underline = _Bit(3)
+ blink = _Bit(4)
+ blink2 = _Bit(5)
+ reverse = _Bit(6)
+ conceal = _Bit(7)
+ strike = _Bit(8)
+ underline2 = _Bit(9)
+ frame = _Bit(10)
+ encircle = _Bit(11)
+ overline = _Bit(12)
+
+ @property
+ def link_id(self) -> str:
+ """Get a link id, used in ansi code for links."""
+ return self._link_id
+
+ def __str__(self) -> str:
+ """Re-generate style definition from attributes."""
+ if self._style_definition is None:
+ attributes: List[str] = []
+ append = attributes.append
+ bits = self._set_attributes
+ if bits & 0b0000000001111:
+ if bits & 1:
+ append("bold" if self.bold else "not bold")
+ if bits & (1 << 1):
+ append("dim" if self.dim else "not dim")
+ if bits & (1 << 2):
+ append("italic" if self.italic else "not italic")
+ if bits & (1 << 3):
+ append("underline" if self.underline else "not underline")
+ if bits & 0b0000111110000:
+ if bits & (1 << 4):
+ append("blink" if self.blink else "not blink")
+ if bits & (1 << 5):
+ append("blink2" if self.blink2 else "not blink2")
+ if bits & (1 << 6):
+ append("reverse" if self.reverse else "not reverse")
+ if bits & (1 << 7):
+ append("conceal" if self.conceal else "not conceal")
+ if bits & (1 << 8):
+ append("strike" if self.strike else "not strike")
+ if bits & 0b1111000000000:
+ if bits & (1 << 9):
+ append("underline2" if self.underline2 else "not underline2")
+ if bits & (1 << 10):
+ append("frame" if self.frame else "not frame")
+ if bits & (1 << 11):
+ append("encircle" if self.encircle else "not encircle")
+ if bits & (1 << 12):
+ append("overline" if self.overline else "not overline")
+ if self._color is not None:
+ append(self._color.name)
+ if self._bgcolor is not None:
+ append("on")
+ append(self._bgcolor.name)
+ if self._link:
+ append("link")
+ append(self._link)
+ self._style_definition = " ".join(attributes) or "none"
+ return self._style_definition
+
+ def __bool__(self) -> bool:
+ """A Style is false if it has no attributes, colors, or links."""
+ return not self._null
+
+ def _make_ansi_codes(self, color_system: ColorSystem) -> str:
+ """Generate ANSI codes for this style.
+
+ Args:
+ color_system (ColorSystem): Color system.
+
+ Returns:
+ str: String containing codes.
+ """
+
+ if self._ansi is None:
+ sgr: List[str] = []
+ append = sgr.append
+ _style_map = self._style_map
+ attributes = self._attributes & self._set_attributes
+ if attributes:
+ if attributes & 1:
+ append(_style_map[0])
+ if attributes & 2:
+ append(_style_map[1])
+ if attributes & 4:
+ append(_style_map[2])
+ if attributes & 8:
+ append(_style_map[3])
+ if attributes & 0b0000111110000:
+ for bit in range(4, 9):
+ if attributes & (1 << bit):
+ append(_style_map[bit])
+ if attributes & 0b1111000000000:
+ for bit in range(9, 13):
+ if attributes & (1 << bit):
+ append(_style_map[bit])
+ if self._color is not None:
+ sgr.extend(self._color.downgrade(color_system).get_ansi_codes())
+ if self._bgcolor is not None:
+ sgr.extend(
+ self._bgcolor.downgrade(color_system).get_ansi_codes(
+ foreground=False
+ )
+ )
+ self._ansi = ";".join(sgr)
+ return self._ansi
+
+ @classmethod
+ @lru_cache(maxsize=1024)
+ def normalize(cls, style: str) -> str:
+ """Normalize a style definition so that styles with the same effect have the same string
+ representation.
+
+ Args:
+ style (str): A style definition.
+
+ Returns:
+ str: Normal form of style definition.
+ """
+ try:
+ return str(cls.parse(style))
+ except errors.StyleSyntaxError:
+ return style.strip().lower()
+
+ @classmethod
+ def pick_first(cls, *values: Optional[StyleType]) -> StyleType:
+ """Pick first non-None style."""
+ for value in values:
+ if value is not None:
+ return value
+ raise ValueError("expected at least one non-None style")
+
+ def __rich_repr__(self) -> Result:
+ yield "color", self.color, None
+ yield "bgcolor", self.bgcolor, None
+ yield "bold", self.bold, None,
+ yield "dim", self.dim, None,
+ yield "italic", self.italic, None
+ yield "underline", self.underline, None,
+ yield "blink", self.blink, None
+ yield "blink2", self.blink2, None
+ yield "reverse", self.reverse, None
+ yield "conceal", self.conceal, None
+ yield "strike", self.strike, None
+ yield "underline2", self.underline2, None
+ yield "frame", self.frame, None
+ yield "encircle", self.encircle, None
+ yield "link", self.link, None
+ if self._meta:
+ yield "meta", self.meta
+
+ def __eq__(self, other: Any) -> bool:
+ if not isinstance(other, Style):
+ return NotImplemented
+ return self.__hash__() == other.__hash__()
+
+ def __ne__(self, other: Any) -> bool:
+ if not isinstance(other, Style):
+ return NotImplemented
+ return self.__hash__() != other.__hash__()
+
+ def __hash__(self) -> int:
+ if self._hash is not None:
+ return self._hash
+ self._hash = hash(
+ (
+ self._color,
+ self._bgcolor,
+ self._attributes,
+ self._set_attributes,
+ self._link,
+ self._meta,
+ )
+ )
+ return self._hash
+
+ @property
+ def color(self) -> Optional[Color]:
+ """The foreground color or None if it is not set."""
+ return self._color
+
+ @property
+ def bgcolor(self) -> Optional[Color]:
+ """The background color or None if it is not set."""
+ return self._bgcolor
+
+ @property
+ def link(self) -> Optional[str]:
+ """Link text, if set."""
+ return self._link
+
+ @property
+ def transparent_background(self) -> bool:
+ """Check if the style specified a transparent background."""
+ return self.bgcolor is None or self.bgcolor.is_default
+
+ @property
+ def background_style(self) -> "Style":
+ """A Style with background only."""
+ return Style(bgcolor=self.bgcolor)
+
+ @property
+ def meta(self) -> Dict[str, Any]:
+ """Get meta information (can not be changed after construction)."""
+ return {} if self._meta is None else cast(Dict[str, Any], loads(self._meta))
+
+ @property
+ def without_color(self) -> "Style":
+ """Get a copy of the style with color removed."""
+ if self._null:
+ return NULL_STYLE
+ style: Style = self.__new__(Style)
+ style._ansi = None
+ style._style_definition = None
+ style._color = None
+ style._bgcolor = None
+ style._attributes = self._attributes
+ style._set_attributes = self._set_attributes
+ style._link = self._link
+ style._link_id = f"{randint(0, 999999)}" if self._link else ""
+ style._null = False
+ style._meta = None
+ style._hash = None
+ return style
+
+ @classmethod
+ @lru_cache(maxsize=4096)
+ def parse(cls, style_definition: str) -> "Style":
+ """Parse a style definition.
+
+ Args:
+ style_definition (str): A string containing a style.
+
+ Raises:
+ errors.StyleSyntaxError: If the style definition syntax is invalid.
+
+ Returns:
+ `Style`: A Style instance.
+ """
+ if style_definition.strip() == "none" or not style_definition:
+ return cls.null()
+
+ STYLE_ATTRIBUTES = cls.STYLE_ATTRIBUTES
+ color: Optional[str] = None
+ bgcolor: Optional[str] = None
+ attributes: Dict[str, Optional[Any]] = {}
+ link: Optional[str] = None
+
+ words = iter(style_definition.split())
+ for original_word in words:
+ word = original_word.lower()
+ if word == "on":
+ word = next(words, "")
+ if not word:
+ raise errors.StyleSyntaxError("color expected after 'on'")
+ try:
+ Color.parse(word) is None
+ except ColorParseError as error:
+ raise errors.StyleSyntaxError(
+ f"unable to parse {word!r} as background color; {error}"
+ ) from None
+ bgcolor = word
+
+ elif word == "not":
+ word = next(words, "")
+ attribute = STYLE_ATTRIBUTES.get(word)
+ if attribute is None:
+ raise errors.StyleSyntaxError(
+ f"expected style attribute after 'not', found {word!r}"
+ )
+ attributes[attribute] = False
+
+ elif word == "link":
+ word = next(words, "")
+ if not word:
+ raise errors.StyleSyntaxError("URL expected after 'link'")
+ link = word
+
+ elif word in STYLE_ATTRIBUTES:
+ attributes[STYLE_ATTRIBUTES[word]] = True
+
+ else:
+ try:
+ Color.parse(word)
+ except ColorParseError as error:
+ raise errors.StyleSyntaxError(
+ f"unable to parse {word!r} as color; {error}"
+ ) from None
+ color = word
+ style = Style(color=color, bgcolor=bgcolor, link=link, **attributes)
+ return style
+
+ @lru_cache(maxsize=1024)
+ def get_html_style(self, theme: Optional[TerminalTheme] = None) -> str:
+ """Get a CSS style rule."""
+ theme = theme or DEFAULT_TERMINAL_THEME
+ css: List[str] = []
+ append = css.append
+
+ color = self.color
+ bgcolor = self.bgcolor
+ if self.reverse:
+ color, bgcolor = bgcolor, color
+ if self.dim:
+ foreground_color = (
+ theme.foreground_color if color is None else color.get_truecolor(theme)
+ )
+ color = Color.from_triplet(
+ blend_rgb(foreground_color, theme.background_color, 0.5)
+ )
+ if color is not None:
+ theme_color = color.get_truecolor(theme)
+ append(f"color: {theme_color.hex}")
+ append(f"text-decoration-color: {theme_color.hex}")
+ if bgcolor is not None:
+ theme_color = bgcolor.get_truecolor(theme, foreground=False)
+ append(f"background-color: {theme_color.hex}")
+ if self.bold:
+ append("font-weight: bold")
+ if self.italic:
+ append("font-style: italic")
+ if self.underline:
+ append("text-decoration: underline")
+ if self.strike:
+ append("text-decoration: line-through")
+ if self.overline:
+ append("text-decoration: overline")
+ return "; ".join(css)
+
+ @classmethod
+ def combine(cls, styles: Iterable["Style"]) -> "Style":
+ """Combine styles and get result.
+
+ Args:
+ styles (Iterable[Style]): Styles to combine.
+
+ Returns:
+ Style: A new style instance.
+ """
+ iter_styles = iter(styles)
+ return sum(iter_styles, next(iter_styles))
+
+ @classmethod
+ def chain(cls, *styles: "Style") -> "Style":
+ """Combine styles from positional argument in to a single style.
+
+ Args:
+ *styles (Iterable[Style]): Styles to combine.
+
+ Returns:
+ Style: A new style instance.
+ """
+ iter_styles = iter(styles)
+ return sum(iter_styles, next(iter_styles))
+
+ def copy(self) -> "Style":
+ """Get a copy of this style.
+
+ Returns:
+ Style: A new Style instance with identical attributes.
+ """
+ if self._null:
+ return NULL_STYLE
+ style: Style = self.__new__(Style)
+ style._ansi = self._ansi
+ style._style_definition = self._style_definition
+ style._color = self._color
+ style._bgcolor = self._bgcolor
+ style._attributes = self._attributes
+ style._set_attributes = self._set_attributes
+ style._link = self._link
+ style._link_id = f"{randint(0, 999999)}" if self._link else ""
+ style._hash = self._hash
+ style._null = False
+ style._meta = self._meta
+ return style
+
+ def update_link(self, link: Optional[str] = None) -> "Style":
+ """Get a copy with a different value for link.
+
+ Args:
+ link (str, optional): New value for link. Defaults to None.
+
+ Returns:
+ Style: A new Style instance.
+ """
+ style: Style = self.__new__(Style)
+ style._ansi = self._ansi
+ style._style_definition = self._style_definition
+ style._color = self._color
+ style._bgcolor = self._bgcolor
+ style._attributes = self._attributes
+ style._set_attributes = self._set_attributes
+ style._link = link
+ style._link_id = f"{randint(0, 999999)}" if link else ""
+ style._hash = None
+ style._null = False
+ style._meta = self._meta
+ return style
+
+ def render(
+ self,
+ text: str = "",
+ *,
+ color_system: Optional[ColorSystem] = ColorSystem.TRUECOLOR,
+ legacy_windows: bool = False,
+ ) -> str:
+ """Render the ANSI codes for the style.
+
+ Args:
+ text (str, optional): A string to style. Defaults to "".
+ color_system (Optional[ColorSystem], optional): Color system to render to. Defaults to ColorSystem.TRUECOLOR.
+
+ Returns:
+ str: A string containing ANSI style codes.
+ """
+ if not text or color_system is None:
+ return text
+ attrs = self._ansi or self._make_ansi_codes(color_system)
+ rendered = f"\x1b[{attrs}m{text}\x1b[0m" if attrs else text
+ if self._link and not legacy_windows:
+ rendered = (
+ f"\x1b]8;id={self._link_id};{self._link}\x1b\\{rendered}\x1b]8;;\x1b\\"
+ )
+ return rendered
+
+ def test(self, text: Optional[str] = None) -> None:
+ """Write text with style directly to terminal.
+
+ This method is for testing purposes only.
+
+ Args:
+ text (Optional[str], optional): Text to style or None for style name.
+
+ """
+ text = text or str(self)
+ sys.stdout.write(f"{self.render(text)}\n")
+
+ @lru_cache(maxsize=1024)
+ def _add(self, style: Optional["Style"]) -> "Style":
+ if style is None or style._null:
+ return self
+ if self._null:
+ return style
+ new_style: Style = self.__new__(Style)
+ new_style._ansi = None
+ new_style._style_definition = None
+ new_style._color = style._color or self._color
+ new_style._bgcolor = style._bgcolor or self._bgcolor
+ new_style._attributes = (self._attributes & ~style._set_attributes) | (
+ style._attributes & style._set_attributes
+ )
+ new_style._set_attributes = self._set_attributes | style._set_attributes
+ new_style._link = style._link or self._link
+ new_style._link_id = style._link_id or self._link_id
+ new_style._null = style._null
+ if self._meta and style._meta:
+ new_style._meta = dumps({**self.meta, **style.meta})
+ else:
+ new_style._meta = self._meta or style._meta
+ new_style._hash = None
+ return new_style
+
+ def __add__(self, style: Optional["Style"]) -> "Style":
+ combined_style = self._add(style)
+ return combined_style.copy() if combined_style.link else combined_style
+
+
+NULL_STYLE = Style()
+
+
+class StyleStack:
+ """A stack of styles."""
+
+ __slots__ = ["_stack"]
+
+ def __init__(self, default_style: "Style") -> None:
+ self._stack: List[Style] = [default_style]
+
+ def __repr__(self) -> str:
+ return f"<stylestack {self._stack!r}>"
+
+ @property
+ def current(self) -> Style:
+ """Get the Style at the top of the stack."""
+ return self._stack[-1]
+
+ def push(self, style: Style) -> None:
+ """Push a new style on to the stack.
+
+ Args:
+ style (Style): New style to combine with current style.
+ """
+ self._stack.append(self._stack[-1] + style)
+
+ def pop(self) -> Style:
+ """Pop last style and discard.
+
+ Returns:
+ Style: New current style (also available as stack.current)
+ """
+ self._stack.pop()
+ return self._stack[-1]
diff --git a/src/pip/_vendor/rich/styled.py b/src/pip/_vendor/rich/styled.py
new file mode 100644
index 000000000..91cd0db31
--- /dev/null
+++ b/src/pip/_vendor/rich/styled.py
@@ -0,0 +1,42 @@
+from typing import TYPE_CHECKING
+
+from .measure import Measurement
+from .segment import Segment
+from .style import StyleType
+
+if TYPE_CHECKING:
+ from .console import Console, ConsoleOptions, RenderResult, RenderableType
+
+
+class Styled:
+ """Apply a style to a renderable.
+
+ Args:
+ renderable (RenderableType): Any renderable.
+ style (StyleType): A style to apply across the entire renderable.
+ """
+
+ def __init__(self, renderable: "RenderableType", style: "StyleType") -> None:
+ self.renderable = renderable
+ self.style = style
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+ style = console.get_style(self.style)
+ rendered_segments = console.render(self.renderable, options)
+ segments = Segment.apply_style(rendered_segments, style)
+ return segments
+
+ def __rich_measure__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> Measurement:
+ return Measurement.get(console, options, self.renderable)
+
+
+if __name__ == "__main__": # pragma: no cover
+ from pip._vendor.rich import print
+ from pip._vendor.rich.panel import Panel
+
+ panel = Styled(Panel("hello"), "on blue")
+ print(panel)
diff --git a/src/pip/_vendor/rich/syntax.py b/src/pip/_vendor/rich/syntax.py
new file mode 100644
index 000000000..01bdd0439
--- /dev/null
+++ b/src/pip/_vendor/rich/syntax.py
@@ -0,0 +1,945 @@
+import os.path
+import platform
+import re
+import sys
+import textwrap
+from abc import ABC, abstractmethod
+from typing import (
+ Any,
+ Dict,
+ Iterable,
+ List,
+ NamedTuple,
+ Optional,
+ Sequence,
+ Set,
+ Tuple,
+ Type,
+ Union,
+)
+
+from pip._vendor.pygments.lexer import Lexer
+from pip._vendor.pygments.lexers import get_lexer_by_name, guess_lexer_for_filename
+from pip._vendor.pygments.style import Style as PygmentsStyle
+from pip._vendor.pygments.styles import get_style_by_name
+from pip._vendor.pygments.token import (
+ Comment,
+ Error,
+ Generic,
+ Keyword,
+ Name,
+ Number,
+ Operator,
+ String,
+ Token,
+ Whitespace,
+)
+from pip._vendor.pygments.util import ClassNotFound
+
+from pip._vendor.rich.containers import Lines
+from pip._vendor.rich.padding import Padding, PaddingDimensions
+
+from ._loop import loop_first
+from .cells import cell_len
+from .color import Color, blend_rgb
+from .console import Console, ConsoleOptions, JustifyMethod, RenderResult
+from .jupyter import JupyterMixin
+from .measure import Measurement
+from .segment import Segment, Segments
+from .style import Style, StyleType
+from .text import Text
+
+TokenType = Tuple[str, ...]
+
+WINDOWS = platform.system() == "Windows"
+DEFAULT_THEME = "monokai"
+
+# The following styles are based on https://github.com/pygments/pygments/blob/master/pygments/formatters/terminal.py
+# A few modifications were made
+
+ANSI_LIGHT: Dict[TokenType, Style] = {
+ Token: Style(),
+ Whitespace: Style(color="white"),
+ Comment: Style(dim=True),
+ Comment.Preproc: Style(color="cyan"),
+ Keyword: Style(color="blue"),
+ Keyword.Type: Style(color="cyan"),
+ Operator.Word: Style(color="magenta"),
+ Name.Builtin: Style(color="cyan"),
+ Name.Function: Style(color="green"),
+ Name.Namespace: Style(color="cyan", underline=True),
+ Name.Class: Style(color="green", underline=True),
+ Name.Exception: Style(color="cyan"),
+ Name.Decorator: Style(color="magenta", bold=True),
+ Name.Variable: Style(color="red"),
+ Name.Constant: Style(color="red"),
+ Name.Attribute: Style(color="cyan"),
+ Name.Tag: Style(color="bright_blue"),
+ String: Style(color="yellow"),
+ Number: Style(color="blue"),
+ Generic.Deleted: Style(color="bright_red"),
+ Generic.Inserted: Style(color="green"),
+ Generic.Heading: Style(bold=True),
+ Generic.Subheading: Style(color="magenta", bold=True),
+ Generic.Prompt: Style(bold=True),
+ Generic.Error: Style(color="bright_red"),
+ Error: Style(color="red", underline=True),
+}
+
+ANSI_DARK: Dict[TokenType, Style] = {
+ Token: Style(),
+ Whitespace: Style(color="bright_black"),
+ Comment: Style(dim=True),
+ Comment.Preproc: Style(color="bright_cyan"),
+ Keyword: Style(color="bright_blue"),
+ Keyword.Type: Style(color="bright_cyan"),
+ Operator.Word: Style(color="bright_magenta"),
+ Name.Builtin: Style(color="bright_cyan"),
+ Name.Function: Style(color="bright_green"),
+ Name.Namespace: Style(color="bright_cyan", underline=True),
+ Name.Class: Style(color="bright_green", underline=True),
+ Name.Exception: Style(color="bright_cyan"),
+ Name.Decorator: Style(color="bright_magenta", bold=True),
+ Name.Variable: Style(color="bright_red"),
+ Name.Constant: Style(color="bright_red"),
+ Name.Attribute: Style(color="bright_cyan"),
+ Name.Tag: Style(color="bright_blue"),
+ String: Style(color="yellow"),
+ Number: Style(color="bright_blue"),
+ Generic.Deleted: Style(color="bright_red"),
+ Generic.Inserted: Style(color="bright_green"),
+ Generic.Heading: Style(bold=True),
+ Generic.Subheading: Style(color="bright_magenta", bold=True),
+ Generic.Prompt: Style(bold=True),
+ Generic.Error: Style(color="bright_red"),
+ Error: Style(color="red", underline=True),
+}
+
+RICH_SYNTAX_THEMES = {"ansi_light": ANSI_LIGHT, "ansi_dark": ANSI_DARK}
+NUMBERS_COLUMN_DEFAULT_PADDING = 2
+
+
+class SyntaxTheme(ABC):
+ """Base class for a syntax theme."""
+
+ @abstractmethod
+ def get_style_for_token(self, token_type: TokenType) -> Style:
+ """Get a style for a given Pygments token."""
+ raise NotImplementedError # pragma: no cover
+
+ @abstractmethod
+ def get_background_style(self) -> Style:
+ """Get the background color."""
+ raise NotImplementedError # pragma: no cover
+
+
+class PygmentsSyntaxTheme(SyntaxTheme):
+ """Syntax theme that delegates to Pygments theme."""
+
+ def __init__(self, theme: Union[str, Type[PygmentsStyle]]) -> None:
+ self._style_cache: Dict[TokenType, Style] = {}
+ if isinstance(theme, str):
+ try:
+ self._pygments_style_class = get_style_by_name(theme)
+ except ClassNotFound:
+ self._pygments_style_class = get_style_by_name("default")
+ else:
+ self._pygments_style_class = theme
+
+ self._background_color = self._pygments_style_class.background_color
+ self._background_style = Style(bgcolor=self._background_color)
+
+ def get_style_for_token(self, token_type: TokenType) -> Style:
+ """Get a style from a Pygments class."""
+ try:
+ return self._style_cache[token_type]
+ except KeyError:
+ try:
+ pygments_style = self._pygments_style_class.style_for_token(token_type)
+ except KeyError:
+ style = Style.null()
+ else:
+ color = pygments_style["color"]
+ bgcolor = pygments_style["bgcolor"]
+ style = Style(
+ color="#" + color if color else "#000000",
+ bgcolor="#" + bgcolor if bgcolor else self._background_color,
+ bold=pygments_style["bold"],
+ italic=pygments_style["italic"],
+ underline=pygments_style["underline"],
+ )
+ self._style_cache[token_type] = style
+ return style
+
+ def get_background_style(self) -> Style:
+ return self._background_style
+
+
+class ANSISyntaxTheme(SyntaxTheme):
+ """Syntax theme to use standard colors."""
+
+ def __init__(self, style_map: Dict[TokenType, Style]) -> None:
+ self.style_map = style_map
+ self._missing_style = Style.null()
+ self._background_style = Style.null()
+ self._style_cache: Dict[TokenType, Style] = {}
+
+ def get_style_for_token(self, token_type: TokenType) -> Style:
+ """Look up style in the style map."""
+ try:
+ return self._style_cache[token_type]
+ except KeyError:
+ # Styles form a hierarchy
+ # We need to go from most to least specific
+ # e.g. ("foo", "bar", "baz") to ("foo", "bar") to ("foo",)
+ get_style = self.style_map.get
+ token = tuple(token_type)
+ style = self._missing_style
+ while token:
+ _style = get_style(token)
+ if _style is not None:
+ style = _style
+ break
+ token = token[:-1]
+ self._style_cache[token_type] = style
+ return style
+
+ def get_background_style(self) -> Style:
+ return self._background_style
+
+
+SyntaxPosition = Tuple[int, int]
+
+
+class _SyntaxHighlightRange(NamedTuple):
+ """
+ A range to highlight in a Syntax object.
+ `start` and `end` are 2-integers tuples, where the first integer is the line number
+ (starting from 1) and the second integer is the column index (starting from 0).
+ """
+
+ style: StyleType
+ start: SyntaxPosition
+ end: SyntaxPosition
+
+
+class Syntax(JupyterMixin):
+ """Construct a Syntax object to render syntax highlighted code.
+
+ Args:
+ code (str): Code to highlight.
+ lexer (Lexer | str): Lexer to use (see https://pygments.org/docs/lexers/)
+ theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "monokai".
+ dedent (bool, optional): Enable stripping of initial whitespace. Defaults to False.
+ line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False.
+ start_line (int, optional): Starting number for line numbers. Defaults to 1.
+ line_range (Tuple[int | None, int | None], optional): If given should be a tuple of the start and end line to render.
+ A value of None in the tuple indicates the range is open in that direction.
+ highlight_lines (Set[int]): A set of line numbers to highlight.
+ code_width: Width of code to render (not including line numbers), or ``None`` to use all available width.
+ tab_size (int, optional): Size of tabs. Defaults to 4.
+ word_wrap (bool, optional): Enable word wrapping.
+ background_color (str, optional): Optional background color, or None to use theme color. Defaults to None.
+ indent_guides (bool, optional): Show indent guides. Defaults to False.
+ padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding).
+ """
+
+ _pygments_style_class: Type[PygmentsStyle]
+ _theme: SyntaxTheme
+
+ @classmethod
+ def get_theme(cls, name: Union[str, SyntaxTheme]) -> SyntaxTheme:
+ """Get a syntax theme instance."""
+ if isinstance(name, SyntaxTheme):
+ return name
+ theme: SyntaxTheme
+ if name in RICH_SYNTAX_THEMES:
+ theme = ANSISyntaxTheme(RICH_SYNTAX_THEMES[name])
+ else:
+ theme = PygmentsSyntaxTheme(name)
+ return theme
+
+ def __init__(
+ self,
+ code: str,
+ lexer: Union[Lexer, str],
+ *,
+ theme: Union[str, SyntaxTheme] = DEFAULT_THEME,
+ dedent: bool = False,
+ line_numbers: bool = False,
+ start_line: int = 1,
+ line_range: Optional[Tuple[Optional[int], Optional[int]]] = None,
+ highlight_lines: Optional[Set[int]] = None,
+ code_width: Optional[int] = None,
+ tab_size: int = 4,
+ word_wrap: bool = False,
+ background_color: Optional[str] = None,
+ indent_guides: bool = False,
+ padding: PaddingDimensions = 0,
+ ) -> None:
+ self.code = code
+ self._lexer = lexer
+ self.dedent = dedent
+ self.line_numbers = line_numbers
+ self.start_line = start_line
+ self.line_range = line_range
+ self.highlight_lines = highlight_lines or set()
+ self.code_width = code_width
+ self.tab_size = tab_size
+ self.word_wrap = word_wrap
+ self.background_color = background_color
+ self.background_style = (
+ Style(bgcolor=background_color) if background_color else Style()
+ )
+ self.indent_guides = indent_guides
+ self.padding = padding
+
+ self._theme = self.get_theme(theme)
+ self._stylized_ranges: List[_SyntaxHighlightRange] = []
+
+ @classmethod
+ def from_path(
+ cls,
+ path: str,
+ encoding: str = "utf-8",
+ lexer: Optional[Union[Lexer, str]] = None,
+ theme: Union[str, SyntaxTheme] = DEFAULT_THEME,
+ dedent: bool = False,
+ line_numbers: bool = False,
+ line_range: Optional[Tuple[int, int]] = None,
+ start_line: int = 1,
+ highlight_lines: Optional[Set[int]] = None,
+ code_width: Optional[int] = None,
+ tab_size: int = 4,
+ word_wrap: bool = False,
+ background_color: Optional[str] = None,
+ indent_guides: bool = False,
+ padding: PaddingDimensions = 0,
+ ) -> "Syntax":
+ """Construct a Syntax object from a file.
+
+ Args:
+ path (str): Path to file to highlight.
+ encoding (str): Encoding of file.
+ lexer (str | Lexer, optional): Lexer to use. If None, lexer will be auto-detected from path/file content.
+ theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "emacs".
+ dedent (bool, optional): Enable stripping of initial whitespace. Defaults to True.
+ line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False.
+ start_line (int, optional): Starting number for line numbers. Defaults to 1.
+ line_range (Tuple[int, int], optional): If given should be a tuple of the start and end line to render.
+ highlight_lines (Set[int]): A set of line numbers to highlight.
+ code_width: Width of code to render (not including line numbers), or ``None`` to use all available width.
+ tab_size (int, optional): Size of tabs. Defaults to 4.
+ word_wrap (bool, optional): Enable word wrapping of code.
+ background_color (str, optional): Optional background color, or None to use theme color. Defaults to None.
+ indent_guides (bool, optional): Show indent guides. Defaults to False.
+ padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding).
+
+ Returns:
+ [Syntax]: A Syntax object that may be printed to the console
+ """
+ with open(path, "rt", encoding=encoding) as code_file:
+ code = code_file.read()
+
+ if not lexer:
+ lexer = cls.guess_lexer(path, code=code)
+
+ return cls(
+ code,
+ lexer,
+ theme=theme,
+ dedent=dedent,
+ line_numbers=line_numbers,
+ line_range=line_range,
+ start_line=start_line,
+ highlight_lines=highlight_lines,
+ code_width=code_width,
+ tab_size=tab_size,
+ word_wrap=word_wrap,
+ background_color=background_color,
+ indent_guides=indent_guides,
+ padding=padding,
+ )
+
+ @classmethod
+ def guess_lexer(cls, path: str, code: Optional[str] = None) -> str:
+ """Guess the alias of the Pygments lexer to use based on a path and an optional string of code.
+ If code is supplied, it will use a combination of the code and the filename to determine the
+ best lexer to use. For example, if the file is ``index.html`` and the file contains Django
+ templating syntax, then "html+django" will be returned. If the file is ``index.html``, and no
+ templating language is used, the "html" lexer will be used. If no string of code
+ is supplied, the lexer will be chosen based on the file extension..
+
+ Args:
+ path (AnyStr): The path to the file containing the code you wish to know the lexer for.
+ code (str, optional): Optional string of code that will be used as a fallback if no lexer
+ is found for the supplied path.
+
+ Returns:
+ str: The name of the Pygments lexer that best matches the supplied path/code.
+ """
+ lexer: Optional[Lexer] = None
+ lexer_name = "default"
+ if code:
+ try:
+ lexer = guess_lexer_for_filename(path, code)
+ except ClassNotFound:
+ pass
+
+ if not lexer:
+ try:
+ _, ext = os.path.splitext(path)
+ if ext:
+ extension = ext.lstrip(".").lower()
+ lexer = get_lexer_by_name(extension)
+ except ClassNotFound:
+ pass
+
+ if lexer:
+ if lexer.aliases:
+ lexer_name = lexer.aliases[0]
+ else:
+ lexer_name = lexer.name
+
+ return lexer_name
+
+ def _get_base_style(self) -> Style:
+ """Get the base style."""
+ default_style = self._theme.get_background_style() + self.background_style
+ return default_style
+
+ def _get_token_color(self, token_type: TokenType) -> Optional[Color]:
+ """Get a color (if any) for the given token.
+
+ Args:
+ token_type (TokenType): A token type tuple from Pygments.
+
+ Returns:
+ Optional[Color]: Color from theme, or None for no color.
+ """
+ style = self._theme.get_style_for_token(token_type)
+ return style.color
+
+ @property
+ def lexer(self) -> Optional[Lexer]:
+ """The lexer for this syntax, or None if no lexer was found.
+
+ Tries to find the lexer by name if a string was passed to the constructor.
+ """
+
+ if isinstance(self._lexer, Lexer):
+ return self._lexer
+ try:
+ return get_lexer_by_name(
+ self._lexer,
+ stripnl=False,
+ ensurenl=True,
+ tabsize=self.tab_size,
+ )
+ except ClassNotFound:
+ return None
+
+ def highlight(
+ self,
+ code: str,
+ line_range: Optional[Tuple[Optional[int], Optional[int]]] = None,
+ ) -> Text:
+ """Highlight code and return a Text instance.
+
+ Args:
+ code (str): Code to highlight.
+ line_range(Tuple[int, int], optional): Optional line range to highlight.
+
+ Returns:
+ Text: A text instance containing highlighted syntax.
+ """
+
+ base_style = self._get_base_style()
+ justify: JustifyMethod = (
+ "default" if base_style.transparent_background else "left"
+ )
+
+ text = Text(
+ justify=justify,
+ style=base_style,
+ tab_size=self.tab_size,
+ no_wrap=not self.word_wrap,
+ )
+ _get_theme_style = self._theme.get_style_for_token
+
+ lexer = self.lexer
+
+ if lexer is None:
+ text.append(code)
+ else:
+ if line_range:
+ # More complicated path to only stylize a portion of the code
+ # This speeds up further operations as there are less spans to process
+ line_start, line_end = line_range
+
+ def line_tokenize() -> Iterable[Tuple[Any, str]]:
+ """Split tokens to one per line."""
+ assert lexer # required to make MyPy happy - we know lexer is not None at this point
+
+ for token_type, token in lexer.get_tokens(code):
+ while token:
+ line_token, new_line, token = token.partition("\n")
+ yield token_type, line_token + new_line
+
+ def tokens_to_spans() -> Iterable[Tuple[str, Optional[Style]]]:
+ """Convert tokens to spans."""
+ tokens = iter(line_tokenize())
+ line_no = 0
+ _line_start = line_start - 1 if line_start else 0
+
+ # Skip over tokens until line start
+ while line_no < _line_start:
+ _token_type, token = next(tokens)
+ yield (token, None)
+ if token.endswith("\n"):
+ line_no += 1
+ # Generate spans until line end
+ for token_type, token in tokens:
+ yield (token, _get_theme_style(token_type))
+ if token.endswith("\n"):
+ line_no += 1
+ if line_end and line_no >= line_end:
+ break
+
+ text.append_tokens(tokens_to_spans())
+
+ else:
+ text.append_tokens(
+ (token, _get_theme_style(token_type))
+ for token_type, token in lexer.get_tokens(code)
+ )
+ if self.background_color is not None:
+ text.stylize(f"on {self.background_color}")
+
+ if self._stylized_ranges:
+ self._apply_stylized_ranges(text)
+
+ return text
+
+ def stylize_range(
+ self, style: StyleType, start: SyntaxPosition, end: SyntaxPosition
+ ) -> None:
+ """
+ Adds a custom style on a part of the code, that will be applied to the syntax display when it's rendered.
+ Line numbers are 1-based, while column indexes are 0-based.
+
+ Args:
+ style (StyleType): The style to apply.
+ start (Tuple[int, int]): The start of the range, in the form `[line number, column index]`.
+ end (Tuple[int, int]): The end of the range, in the form `[line number, column index]`.
+ """
+ self._stylized_ranges.append(_SyntaxHighlightRange(style, start, end))
+
+ def _get_line_numbers_color(self, blend: float = 0.3) -> Color:
+ background_style = self._theme.get_background_style() + self.background_style
+ background_color = background_style.bgcolor
+ if background_color is None or background_color.is_system_defined:
+ return Color.default()
+ foreground_color = self._get_token_color(Token.Text)
+ if foreground_color is None or foreground_color.is_system_defined:
+ return foreground_color or Color.default()
+ new_color = blend_rgb(
+ background_color.get_truecolor(),
+ foreground_color.get_truecolor(),
+ cross_fade=blend,
+ )
+ return Color.from_triplet(new_color)
+
+ @property
+ def _numbers_column_width(self) -> int:
+ """Get the number of characters used to render the numbers column."""
+ column_width = 0
+ if self.line_numbers:
+ column_width = (
+ len(str(self.start_line + self.code.count("\n")))
+ + NUMBERS_COLUMN_DEFAULT_PADDING
+ )
+ return column_width
+
+ def _get_number_styles(self, console: Console) -> Tuple[Style, Style, Style]:
+ """Get background, number, and highlight styles for line numbers."""
+ background_style = self._get_base_style()
+ if background_style.transparent_background:
+ return Style.null(), Style(dim=True), Style.null()
+ if console.color_system in ("256", "truecolor"):
+ number_style = Style.chain(
+ background_style,
+ self._theme.get_style_for_token(Token.Text),
+ Style(color=self._get_line_numbers_color()),
+ self.background_style,
+ )
+ highlight_number_style = Style.chain(
+ background_style,
+ self._theme.get_style_for_token(Token.Text),
+ Style(bold=True, color=self._get_line_numbers_color(0.9)),
+ self.background_style,
+ )
+ else:
+ number_style = background_style + Style(dim=True)
+ highlight_number_style = background_style + Style(dim=False)
+ return background_style, number_style, highlight_number_style
+
+ def __rich_measure__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "Measurement":
+
+ _, right, _, left = Padding.unpack(self.padding)
+ padding = left + right
+ if self.code_width is not None:
+ width = self.code_width + self._numbers_column_width + padding + 1
+ return Measurement(self._numbers_column_width, width)
+ lines = self.code.splitlines()
+ width = (
+ self._numbers_column_width
+ + padding
+ + (max(cell_len(line) for line in lines) if lines else 0)
+ )
+ if self.line_numbers:
+ width += 1
+ return Measurement(self._numbers_column_width, width)
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ segments = Segments(self._get_syntax(console, options))
+ if self.padding:
+ yield Padding(
+ segments, style=self._theme.get_background_style(), pad=self.padding
+ )
+ else:
+ yield segments
+
+ def _get_syntax(
+ self,
+ console: Console,
+ options: ConsoleOptions,
+ ) -> Iterable[Segment]:
+ """
+ Get the Segments for the Syntax object, excluding any vertical/horizontal padding
+ """
+ transparent_background = self._get_base_style().transparent_background
+ code_width = (
+ (
+ (options.max_width - self._numbers_column_width - 1)
+ if self.line_numbers
+ else options.max_width
+ )
+ if self.code_width is None
+ else self.code_width
+ )
+
+ ends_on_nl, processed_code = self._process_code(self.code)
+ text = self.highlight(processed_code, self.line_range)
+
+ if not self.line_numbers and not self.word_wrap and not self.line_range:
+ if not ends_on_nl:
+ text.remove_suffix("\n")
+ # Simple case of just rendering text
+ style = (
+ self._get_base_style()
+ + self._theme.get_style_for_token(Comment)
+ + Style(dim=True)
+ + self.background_style
+ )
+ if self.indent_guides and not options.ascii_only:
+ text = text.with_indent_guides(self.tab_size, style=style)
+ text.overflow = "crop"
+ if style.transparent_background:
+ yield from console.render(
+ text, options=options.update(width=code_width)
+ )
+ else:
+ syntax_lines = console.render_lines(
+ text,
+ options.update(width=code_width, height=None, justify="left"),
+ style=self.background_style,
+ pad=True,
+ new_lines=True,
+ )
+ for syntax_line in syntax_lines:
+ yield from syntax_line
+ return
+
+ start_line, end_line = self.line_range or (None, None)
+ line_offset = 0
+ if start_line:
+ line_offset = max(0, start_line - 1)
+ lines: Union[List[Text], Lines] = text.split("\n", allow_blank=ends_on_nl)
+ if self.line_range:
+ lines = lines[line_offset:end_line]
+
+ if self.indent_guides and not options.ascii_only:
+ style = (
+ self._get_base_style()
+ + self._theme.get_style_for_token(Comment)
+ + Style(dim=True)
+ + self.background_style
+ )
+ lines = (
+ Text("\n")
+ .join(lines)
+ .with_indent_guides(self.tab_size, style=style)
+ .split("\n", allow_blank=True)
+ )
+
+ numbers_column_width = self._numbers_column_width
+ render_options = options.update(width=code_width)
+
+ highlight_line = self.highlight_lines.__contains__
+ _Segment = Segment
+ new_line = _Segment("\n")
+
+ line_pointer = "> " if options.legacy_windows else "❱ "
+
+ (
+ background_style,
+ number_style,
+ highlight_number_style,
+ ) = self._get_number_styles(console)
+
+ for line_no, line in enumerate(lines, self.start_line + line_offset):
+ if self.word_wrap:
+ wrapped_lines = console.render_lines(
+ line,
+ render_options.update(height=None, justify="left"),
+ style=background_style,
+ pad=not transparent_background,
+ )
+ else:
+ segments = list(line.render(console, end=""))
+ if options.no_wrap:
+ wrapped_lines = [segments]
+ else:
+ wrapped_lines = [
+ _Segment.adjust_line_length(
+ segments,
+ render_options.max_width,
+ style=background_style,
+ pad=not transparent_background,
+ )
+ ]
+
+ if self.line_numbers:
+ wrapped_line_left_pad = _Segment(
+ " " * numbers_column_width + " ", background_style
+ )
+ for first, wrapped_line in loop_first(wrapped_lines):
+ if first:
+ line_column = str(line_no).rjust(numbers_column_width - 2) + " "
+ if highlight_line(line_no):
+ yield _Segment(line_pointer, Style(color="red"))
+ yield _Segment(line_column, highlight_number_style)
+ else:
+ yield _Segment(" ", highlight_number_style)
+ yield _Segment(line_column, number_style)
+ else:
+ yield wrapped_line_left_pad
+ yield from wrapped_line
+ yield new_line
+ else:
+ for wrapped_line in wrapped_lines:
+ yield from wrapped_line
+ yield new_line
+
+ def _apply_stylized_ranges(self, text: Text) -> None:
+ """
+ Apply stylized ranges to a text instance,
+ using the given code to determine the right portion to apply the style to.
+
+ Args:
+ text (Text): Text instance to apply the style to.
+ """
+ code = text.plain
+ newlines_offsets = [
+ # Let's add outer boundaries at each side of the list:
+ 0,
+ # N.B. using "\n" here is much faster than using metacharacters such as "^" or "\Z":
+ *[
+ match.start() + 1
+ for match in re.finditer("\n", code, flags=re.MULTILINE)
+ ],
+ len(code) + 1,
+ ]
+
+ for stylized_range in self._stylized_ranges:
+ start = _get_code_index_for_syntax_position(
+ newlines_offsets, stylized_range.start
+ )
+ end = _get_code_index_for_syntax_position(
+ newlines_offsets, stylized_range.end
+ )
+ if start is not None and end is not None:
+ text.stylize(stylized_range.style, start, end)
+
+ def _process_code(self, code: str) -> Tuple[bool, str]:
+ """
+ Applies various processing to a raw code string
+ (normalises it so it always ends with a line return, dedents it if necessary, etc.)
+
+ Args:
+ code (str): The raw code string to process
+
+ Returns:
+ Tuple[bool, str]: the boolean indicates whether the raw code ends with a line return,
+ while the string is the processed code.
+ """
+ ends_on_nl = code.endswith("\n")
+ processed_code = code if ends_on_nl else code + "\n"
+ processed_code = (
+ textwrap.dedent(processed_code) if self.dedent else processed_code
+ )
+ processed_code = processed_code.expandtabs(self.tab_size)
+ return ends_on_nl, processed_code
+
+
+def _get_code_index_for_syntax_position(
+ newlines_offsets: Sequence[int], position: SyntaxPosition
+) -> Optional[int]:
+ """
+ Returns the index of the code string for the given positions.
+
+ Args:
+ newlines_offsets (Sequence[int]): The offset of each newline character found in the code snippet.
+ position (SyntaxPosition): The position to search for.
+
+ Returns:
+ Optional[int]: The index of the code string for this position, or `None`
+ if the given position's line number is out of range (if it's the column that is out of range
+ we silently clamp its value so that it reaches the end of the line)
+ """
+ lines_count = len(newlines_offsets)
+
+ line_number, column_index = position
+ if line_number > lines_count or len(newlines_offsets) < (line_number + 1):
+ return None # `line_number` is out of range
+ line_index = line_number - 1
+ line_length = newlines_offsets[line_index + 1] - newlines_offsets[line_index] - 1
+ # If `column_index` is out of range: let's silently clamp it:
+ column_index = min(line_length, column_index)
+ return newlines_offsets[line_index] + column_index
+
+
+if __name__ == "__main__": # pragma: no cover
+
+ import argparse
+ import sys
+
+ parser = argparse.ArgumentParser(
+ description="Render syntax to the console with Rich"
+ )
+ parser.add_argument(
+ "path",
+ metavar="PATH",
+ help="path to file, or - for stdin",
+ )
+ parser.add_argument(
+ "-c",
+ "--force-color",
+ dest="force_color",
+ action="store_true",
+ default=None,
+ help="force color for non-terminals",
+ )
+ parser.add_argument(
+ "-i",
+ "--indent-guides",
+ dest="indent_guides",
+ action="store_true",
+ default=False,
+ help="display indent guides",
+ )
+ parser.add_argument(
+ "-l",
+ "--line-numbers",
+ dest="line_numbers",
+ action="store_true",
+ help="render line numbers",
+ )
+ parser.add_argument(
+ "-w",
+ "--width",
+ type=int,
+ dest="width",
+ default=None,
+ help="width of output (default will auto-detect)",
+ )
+ parser.add_argument(
+ "-r",
+ "--wrap",
+ dest="word_wrap",
+ action="store_true",
+ default=False,
+ help="word wrap long lines",
+ )
+ parser.add_argument(
+ "-s",
+ "--soft-wrap",
+ action="store_true",
+ dest="soft_wrap",
+ default=False,
+ help="enable soft wrapping mode",
+ )
+ parser.add_argument(
+ "-t", "--theme", dest="theme", default="monokai", help="pygments theme"
+ )
+ parser.add_argument(
+ "-b",
+ "--background-color",
+ dest="background_color",
+ default=None,
+ help="Override background color",
+ )
+ parser.add_argument(
+ "-x",
+ "--lexer",
+ default=None,
+ dest="lexer_name",
+ help="Lexer name",
+ )
+ parser.add_argument(
+ "-p", "--padding", type=int, default=0, dest="padding", help="Padding"
+ )
+ parser.add_argument(
+ "--highlight-line",
+ type=int,
+ default=None,
+ dest="highlight_line",
+ help="The line number (not index!) to highlight",
+ )
+ args = parser.parse_args()
+
+ from pip._vendor.rich.console import Console
+
+ console = Console(force_terminal=args.force_color, width=args.width)
+
+ if args.path == "-":
+ code = sys.stdin.read()
+ syntax = Syntax(
+ code=code,
+ lexer=args.lexer_name,
+ line_numbers=args.line_numbers,
+ word_wrap=args.word_wrap,
+ theme=args.theme,
+ background_color=args.background_color,
+ indent_guides=args.indent_guides,
+ padding=args.padding,
+ highlight_lines={args.highlight_line},
+ )
+ else:
+ syntax = Syntax.from_path(
+ args.path,
+ lexer=args.lexer_name,
+ line_numbers=args.line_numbers,
+ word_wrap=args.word_wrap,
+ theme=args.theme,
+ background_color=args.background_color,
+ indent_guides=args.indent_guides,
+ padding=args.padding,
+ highlight_lines={args.highlight_line},
+ )
+ console.print(syntax, soft_wrap=args.soft_wrap)
diff --git a/src/pip/_vendor/rich/table.py b/src/pip/_vendor/rich/table.py
new file mode 100644
index 000000000..17409f2ee
--- /dev/null
+++ b/src/pip/_vendor/rich/table.py
@@ -0,0 +1,1002 @@
+from dataclasses import dataclass, field, replace
+from typing import (
+ TYPE_CHECKING,
+ Dict,
+ Iterable,
+ List,
+ NamedTuple,
+ Optional,
+ Sequence,
+ Tuple,
+ Union,
+)
+
+from . import box, errors
+from ._loop import loop_first_last, loop_last
+from ._pick import pick_bool
+from ._ratio import ratio_distribute, ratio_reduce
+from .align import VerticalAlignMethod
+from .jupyter import JupyterMixin
+from .measure import Measurement
+from .padding import Padding, PaddingDimensions
+from .protocol import is_renderable
+from .segment import Segment
+from .style import Style, StyleType
+from .text import Text, TextType
+
+if TYPE_CHECKING:
+ from .console import (
+ Console,
+ ConsoleOptions,
+ JustifyMethod,
+ OverflowMethod,
+ RenderableType,
+ RenderResult,
+ )
+
+
+@dataclass
+class Column:
+ """Defines a column within a ~Table.
+
+ Args:
+ title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None.
+ caption (Union[str, Text], optional): The table caption rendered below. Defaults to None.
+ width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None.
+ min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None.
+ box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD.
+ safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
+ padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1).
+ collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False.
+ pad_edge (bool, optional): Enable padding of edge cells. Defaults to True.
+ expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.
+ show_header (bool, optional): Show a header row. Defaults to True.
+ show_footer (bool, optional): Show a footer row. Defaults to False.
+ show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True.
+ show_lines (bool, optional): Draw lines between every row. Defaults to False.
+ leading (bool, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0.
+ style (Union[str, Style], optional): Default style for the table. Defaults to "none".
+ row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None.
+ header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header".
+ footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer".
+ border_style (Union[str, Style], optional): Style of the border. Defaults to None.
+ title_style (Union[str, Style], optional): Style of the title. Defaults to None.
+ caption_style (Union[str, Style], optional): Style of the caption. Defaults to None.
+ title_justify (str, optional): Justify method for title. Defaults to "center".
+ caption_justify (str, optional): Justify method for caption. Defaults to "center".
+ highlight (bool, optional): Highlight cell contents (if str). Defaults to False.
+ """
+
+ header: "RenderableType" = ""
+ """RenderableType: Renderable for the header (typically a string)"""
+
+ footer: "RenderableType" = ""
+ """RenderableType: Renderable for the footer (typically a string)"""
+
+ header_style: StyleType = ""
+ """StyleType: The style of the header."""
+
+ footer_style: StyleType = ""
+ """StyleType: The style of the footer."""
+
+ style: StyleType = ""
+ """StyleType: The style of the column."""
+
+ justify: "JustifyMethod" = "left"
+ """str: How to justify text within the column ("left", "center", "right", or "full")"""
+
+ vertical: "VerticalAlignMethod" = "top"
+ """str: How to vertically align content ("top", "middle", or "bottom")"""
+
+ overflow: "OverflowMethod" = "ellipsis"
+ """str: Overflow method."""
+
+ width: Optional[int] = None
+ """Optional[int]: Width of the column, or ``None`` (default) to auto calculate width."""
+
+ min_width: Optional[int] = None
+ """Optional[int]: Minimum width of column, or ``None`` for no minimum. Defaults to None."""
+
+ max_width: Optional[int] = None
+ """Optional[int]: Maximum width of column, or ``None`` for no maximum. Defaults to None."""
+
+ ratio: Optional[int] = None
+ """Optional[int]: Ratio to use when calculating column width, or ``None`` (default) to adapt to column contents."""
+
+ no_wrap: bool = False
+ """bool: Prevent wrapping of text within the column. Defaults to ``False``."""
+
+ _index: int = 0
+ """Index of column."""
+
+ _cells: List["RenderableType"] = field(default_factory=list)
+
+ def copy(self) -> "Column":
+ """Return a copy of this Column."""
+ return replace(self, _cells=[])
+
+ @property
+ def cells(self) -> Iterable["RenderableType"]:
+ """Get all cells in the column, not including header."""
+ yield from self._cells
+
+ @property
+ def flexible(self) -> bool:
+ """Check if this column is flexible."""
+ return self.ratio is not None
+
+
+@dataclass
+class Row:
+ """Information regarding a row."""
+
+ style: Optional[StyleType] = None
+ """Style to apply to row."""
+
+ end_section: bool = False
+ """Indicated end of section, which will force a line beneath the row."""
+
+
+class _Cell(NamedTuple):
+ """A single cell in a table."""
+
+ style: StyleType
+ """Style to apply to cell."""
+ renderable: "RenderableType"
+ """Cell renderable."""
+ vertical: VerticalAlignMethod
+ """Cell vertical alignment."""
+
+
+class Table(JupyterMixin):
+ """A console renderable to draw a table.
+
+ Args:
+ *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance.
+ title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None.
+ caption (Union[str, Text], optional): The table caption rendered below. Defaults to None.
+ width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None.
+ min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None.
+ box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD.
+ safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
+ padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1).
+ collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False.
+ pad_edge (bool, optional): Enable padding of edge cells. Defaults to True.
+ expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.
+ show_header (bool, optional): Show a header row. Defaults to True.
+ show_footer (bool, optional): Show a footer row. Defaults to False.
+ show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True.
+ show_lines (bool, optional): Draw lines between every row. Defaults to False.
+ leading (bool, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0.
+ style (Union[str, Style], optional): Default style for the table. Defaults to "none".
+ row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None.
+ header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header".
+ footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer".
+ border_style (Union[str, Style], optional): Style of the border. Defaults to None.
+ title_style (Union[str, Style], optional): Style of the title. Defaults to None.
+ caption_style (Union[str, Style], optional): Style of the caption. Defaults to None.
+ title_justify (str, optional): Justify method for title. Defaults to "center".
+ caption_justify (str, optional): Justify method for caption. Defaults to "center".
+ highlight (bool, optional): Highlight cell contents (if str). Defaults to False.
+ """
+
+ columns: List[Column]
+ rows: List[Row]
+
+ def __init__(
+ self,
+ *headers: Union[Column, str],
+ title: Optional[TextType] = None,
+ caption: Optional[TextType] = None,
+ width: Optional[int] = None,
+ min_width: Optional[int] = None,
+ box: Optional[box.Box] = box.HEAVY_HEAD,
+ safe_box: Optional[bool] = None,
+ padding: PaddingDimensions = (0, 1),
+ collapse_padding: bool = False,
+ pad_edge: bool = True,
+ expand: bool = False,
+ show_header: bool = True,
+ show_footer: bool = False,
+ show_edge: bool = True,
+ show_lines: bool = False,
+ leading: int = 0,
+ style: StyleType = "none",
+ row_styles: Optional[Iterable[StyleType]] = None,
+ header_style: Optional[StyleType] = "table.header",
+ footer_style: Optional[StyleType] = "table.footer",
+ border_style: Optional[StyleType] = None,
+ title_style: Optional[StyleType] = None,
+ caption_style: Optional[StyleType] = None,
+ title_justify: "JustifyMethod" = "center",
+ caption_justify: "JustifyMethod" = "center",
+ highlight: bool = False,
+ ) -> None:
+
+ self.columns: List[Column] = []
+ self.rows: List[Row] = []
+ self.title = title
+ self.caption = caption
+ self.width = width
+ self.min_width = min_width
+ self.box = box
+ self.safe_box = safe_box
+ self._padding = Padding.unpack(padding)
+ self.pad_edge = pad_edge
+ self._expand = expand
+ self.show_header = show_header
+ self.show_footer = show_footer
+ self.show_edge = show_edge
+ self.show_lines = show_lines
+ self.leading = leading
+ self.collapse_padding = collapse_padding
+ self.style = style
+ self.header_style = header_style or ""
+ self.footer_style = footer_style or ""
+ self.border_style = border_style
+ self.title_style = title_style
+ self.caption_style = caption_style
+ self.title_justify: "JustifyMethod" = title_justify
+ self.caption_justify: "JustifyMethod" = caption_justify
+ self.highlight = highlight
+ self.row_styles: Sequence[StyleType] = list(row_styles or [])
+ append_column = self.columns.append
+ for header in headers:
+ if isinstance(header, str):
+ self.add_column(header=header)
+ else:
+ header._index = len(self.columns)
+ append_column(header)
+
+ @classmethod
+ def grid(
+ cls,
+ *headers: Union[Column, str],
+ padding: PaddingDimensions = 0,
+ collapse_padding: bool = True,
+ pad_edge: bool = False,
+ expand: bool = False,
+ ) -> "Table":
+ """Get a table with no lines, headers, or footer.
+
+ Args:
+ *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance.
+ padding (PaddingDimensions, optional): Get padding around cells. Defaults to 0.
+ collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to True.
+ pad_edge (bool, optional): Enable padding around edges of table. Defaults to False.
+ expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.
+
+ Returns:
+ Table: A table instance.
+ """
+ return cls(
+ *headers,
+ box=None,
+ padding=padding,
+ collapse_padding=collapse_padding,
+ show_header=False,
+ show_footer=False,
+ show_edge=False,
+ pad_edge=pad_edge,
+ expand=expand,
+ )
+
+ @property
+ def expand(self) -> bool:
+ """Setting a non-None self.width implies expand."""
+ return self._expand or self.width is not None
+
+ @expand.setter
+ def expand(self, expand: bool) -> None:
+ """Set expand."""
+ self._expand = expand
+
+ @property
+ def _extra_width(self) -> int:
+ """Get extra width to add to cell content."""
+ width = 0
+ if self.box and self.show_edge:
+ width += 2
+ if self.box:
+ width += len(self.columns) - 1
+ return width
+
+ @property
+ def row_count(self) -> int:
+ """Get the current number of rows."""
+ return len(self.rows)
+
+ def get_row_style(self, console: "Console", index: int) -> StyleType:
+ """Get the current row style."""
+ style = Style.null()
+ if self.row_styles:
+ style += console.get_style(self.row_styles[index % len(self.row_styles)])
+ row_style = self.rows[index].style
+ if row_style is not None:
+ style += console.get_style(row_style)
+ return style
+
+ def __rich_measure__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> Measurement:
+ max_width = options.max_width
+ if self.width is not None:
+ max_width = self.width
+ if max_width < 0:
+ return Measurement(0, 0)
+
+ extra_width = self._extra_width
+ max_width = sum(
+ self._calculate_column_widths(
+ console, options.update_width(max_width - extra_width)
+ )
+ )
+ _measure_column = self._measure_column
+
+ measurements = [
+ _measure_column(console, options.update_width(max_width), column)
+ for column in self.columns
+ ]
+ minimum_width = (
+ sum(measurement.minimum for measurement in measurements) + extra_width
+ )
+ maximum_width = (
+ sum(measurement.maximum for measurement in measurements) + extra_width
+ if (self.width is None)
+ else self.width
+ )
+ measurement = Measurement(minimum_width, maximum_width)
+ measurement = measurement.clamp(self.min_width)
+ return measurement
+
+ @property
+ def padding(self) -> Tuple[int, int, int, int]:
+ """Get cell padding."""
+ return self._padding
+
+ @padding.setter
+ def padding(self, padding: PaddingDimensions) -> "Table":
+ """Set cell padding."""
+ self._padding = Padding.unpack(padding)
+ return self
+
+ def add_column(
+ self,
+ header: "RenderableType" = "",
+ footer: "RenderableType" = "",
+ *,
+ header_style: Optional[StyleType] = None,
+ footer_style: Optional[StyleType] = None,
+ style: Optional[StyleType] = None,
+ justify: "JustifyMethod" = "left",
+ vertical: "VerticalAlignMethod" = "top",
+ overflow: "OverflowMethod" = "ellipsis",
+ width: Optional[int] = None,
+ min_width: Optional[int] = None,
+ max_width: Optional[int] = None,
+ ratio: Optional[int] = None,
+ no_wrap: bool = False,
+ ) -> None:
+ """Add a column to the table.
+
+ Args:
+ header (RenderableType, optional): Text or renderable for the header.
+ Defaults to "".
+ footer (RenderableType, optional): Text or renderable for the footer.
+ Defaults to "".
+ header_style (Union[str, Style], optional): Style for the header, or None for default. Defaults to None.
+ footer_style (Union[str, Style], optional): Style for the footer, or None for default. Defaults to None.
+ style (Union[str, Style], optional): Style for the column cells, or None for default. Defaults to None.
+ justify (JustifyMethod, optional): Alignment for cells. Defaults to "left".
+ vertical (VerticalAlignMethod, optional): Vertical alignment, one of "top", "middle", or "bottom". Defaults to "top".
+ overflow (OverflowMethod): Overflow method: "crop", "fold", "ellipsis". Defaults to "ellipsis".
+ width (int, optional): Desired width of column in characters, or None to fit to contents. Defaults to None.
+ min_width (Optional[int], optional): Minimum width of column, or ``None`` for no minimum. Defaults to None.
+ max_width (Optional[int], optional): Maximum width of column, or ``None`` for no maximum. Defaults to None.
+ ratio (int, optional): Flexible ratio for the column (requires ``Table.expand`` or ``Table.width``). Defaults to None.
+ no_wrap (bool, optional): Set to ``True`` to disable wrapping of this column.
+ """
+
+ column = Column(
+ _index=len(self.columns),
+ header=header,
+ footer=footer,
+ header_style=header_style or "",
+ footer_style=footer_style or "",
+ style=style or "",
+ justify=justify,
+ vertical=vertical,
+ overflow=overflow,
+ width=width,
+ min_width=min_width,
+ max_width=max_width,
+ ratio=ratio,
+ no_wrap=no_wrap,
+ )
+ self.columns.append(column)
+
+ def add_row(
+ self,
+ *renderables: Optional["RenderableType"],
+ style: Optional[StyleType] = None,
+ end_section: bool = False,
+ ) -> None:
+ """Add a row of renderables.
+
+ Args:
+ *renderables (None or renderable): Each cell in a row must be a renderable object (including str),
+ or ``None`` for a blank cell.
+ style (StyleType, optional): An optional style to apply to the entire row. Defaults to None.
+ end_section (bool, optional): End a section and draw a line. Defaults to False.
+
+ Raises:
+ errors.NotRenderableError: If you add something that can't be rendered.
+ """
+
+ def add_cell(column: Column, renderable: "RenderableType") -> None:
+ column._cells.append(renderable)
+
+ cell_renderables: List[Optional["RenderableType"]] = list(renderables)
+
+ columns = self.columns
+ if len(cell_renderables) < len(columns):
+ cell_renderables = [
+ *cell_renderables,
+ *[None] * (len(columns) - len(cell_renderables)),
+ ]
+ for index, renderable in enumerate(cell_renderables):
+ if index == len(columns):
+ column = Column(_index=index)
+ for _ in self.rows:
+ add_cell(column, Text(""))
+ self.columns.append(column)
+ else:
+ column = columns[index]
+ if renderable is None:
+ add_cell(column, "")
+ elif is_renderable(renderable):
+ add_cell(column, renderable)
+ else:
+ raise errors.NotRenderableError(
+ f"unable to render {type(renderable).__name__}; a string or other renderable object is required"
+ )
+ self.rows.append(Row(style=style, end_section=end_section))
+
+ def add_section(self) -> None:
+ """Add a new section (draw a line after current row)."""
+
+ if self.rows:
+ self.rows[-1].end_section = True
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+
+ if not self.columns:
+ yield Segment("\n")
+ return
+
+ max_width = options.max_width
+ if self.width is not None:
+ max_width = self.width
+
+ extra_width = self._extra_width
+ widths = self._calculate_column_widths(
+ console, options.update_width(max_width - extra_width)
+ )
+ table_width = sum(widths) + extra_width
+
+ render_options = options.update(
+ width=table_width, highlight=self.highlight, height=None
+ )
+
+ def render_annotation(
+ text: TextType, style: StyleType, justify: "JustifyMethod" = "center"
+ ) -> "RenderResult":
+ render_text = (
+ console.render_str(text, style=style, highlight=False)
+ if isinstance(text, str)
+ else text
+ )
+ return console.render(
+ render_text, options=render_options.update(justify=justify)
+ )
+
+ if self.title:
+ yield from render_annotation(
+ self.title,
+ style=Style.pick_first(self.title_style, "table.title"),
+ justify=self.title_justify,
+ )
+ yield from self._render(console, render_options, widths)
+ if self.caption:
+ yield from render_annotation(
+ self.caption,
+ style=Style.pick_first(self.caption_style, "table.caption"),
+ justify=self.caption_justify,
+ )
+
+ def _calculate_column_widths(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> List[int]:
+ """Calculate the widths of each column, including padding, not including borders."""
+ max_width = options.max_width
+ columns = self.columns
+ width_ranges = [
+ self._measure_column(console, options, column) for column in columns
+ ]
+ widths = [_range.maximum or 1 for _range in width_ranges]
+ get_padding_width = self._get_padding_width
+ extra_width = self._extra_width
+ if self.expand:
+ ratios = [col.ratio or 0 for col in columns if col.flexible]
+ if any(ratios):
+ fixed_widths = [
+ 0 if column.flexible else _range.maximum
+ for _range, column in zip(width_ranges, columns)
+ ]
+ flex_minimum = [
+ (column.width or 1) + get_padding_width(column._index)
+ for column in columns
+ if column.flexible
+ ]
+ flexible_width = max_width - sum(fixed_widths)
+ flex_widths = ratio_distribute(flexible_width, ratios, flex_minimum)
+ iter_flex_widths = iter(flex_widths)
+ for index, column in enumerate(columns):
+ if column.flexible:
+ widths[index] = fixed_widths[index] + next(iter_flex_widths)
+ table_width = sum(widths)
+
+ if table_width > max_width:
+ widths = self._collapse_widths(
+ widths,
+ [(column.width is None and not column.no_wrap) for column in columns],
+ max_width,
+ )
+ table_width = sum(widths)
+ # last resort, reduce columns evenly
+ if table_width > max_width:
+ excess_width = table_width - max_width
+ widths = ratio_reduce(excess_width, [1] * len(widths), widths, widths)
+ table_width = sum(widths)
+
+ width_ranges = [
+ self._measure_column(console, options.update_width(width), column)
+ for width, column in zip(widths, columns)
+ ]
+ widths = [_range.maximum or 0 for _range in width_ranges]
+
+ if (table_width < max_width and self.expand) or (
+ self.min_width is not None and table_width < (self.min_width - extra_width)
+ ):
+ _max_width = (
+ max_width
+ if self.min_width is None
+ else min(self.min_width - extra_width, max_width)
+ )
+ pad_widths = ratio_distribute(_max_width - table_width, widths)
+ widths = [_width + pad for _width, pad in zip(widths, pad_widths)]
+
+ return widths
+
+ @classmethod
+ def _collapse_widths(
+ cls, widths: List[int], wrapable: List[bool], max_width: int
+ ) -> List[int]:
+ """Reduce widths so that the total is under max_width.
+
+ Args:
+ widths (List[int]): List of widths.
+ wrapable (List[bool]): List of booleans that indicate if a column may shrink.
+ max_width (int): Maximum width to reduce to.
+
+ Returns:
+ List[int]: A new list of widths.
+ """
+ total_width = sum(widths)
+ excess_width = total_width - max_width
+ if any(wrapable):
+ while total_width and excess_width > 0:
+ max_column = max(
+ width for width, allow_wrap in zip(widths, wrapable) if allow_wrap
+ )
+ second_max_column = max(
+ width if allow_wrap and width != max_column else 0
+ for width, allow_wrap in zip(widths, wrapable)
+ )
+ column_difference = max_column - second_max_column
+ ratios = [
+ (1 if (width == max_column and allow_wrap) else 0)
+ for width, allow_wrap in zip(widths, wrapable)
+ ]
+ if not any(ratios) or not column_difference:
+ break
+ max_reduce = [min(excess_width, column_difference)] * len(widths)
+ widths = ratio_reduce(excess_width, ratios, max_reduce, widths)
+
+ total_width = sum(widths)
+ excess_width = total_width - max_width
+ return widths
+
+ def _get_cells(
+ self, console: "Console", column_index: int, column: Column
+ ) -> Iterable[_Cell]:
+ """Get all the cells with padding and optional header."""
+
+ collapse_padding = self.collapse_padding
+ pad_edge = self.pad_edge
+ padding = self.padding
+ any_padding = any(padding)
+
+ first_column = column_index == 0
+ last_column = column_index == len(self.columns) - 1
+
+ _padding_cache: Dict[Tuple[bool, bool], Tuple[int, int, int, int]] = {}
+
+ def get_padding(first_row: bool, last_row: bool) -> Tuple[int, int, int, int]:
+ cached = _padding_cache.get((first_row, last_row))
+ if cached:
+ return cached
+ top, right, bottom, left = padding
+
+ if collapse_padding:
+ if not first_column:
+ left = max(0, left - right)
+ if not last_row:
+ bottom = max(0, top - bottom)
+
+ if not pad_edge:
+ if first_column:
+ left = 0
+ if last_column:
+ right = 0
+ if first_row:
+ top = 0
+ if last_row:
+ bottom = 0
+ _padding = (top, right, bottom, left)
+ _padding_cache[(first_row, last_row)] = _padding
+ return _padding
+
+ raw_cells: List[Tuple[StyleType, "RenderableType"]] = []
+ _append = raw_cells.append
+ get_style = console.get_style
+ if self.show_header:
+ header_style = get_style(self.header_style or "") + get_style(
+ column.header_style
+ )
+ _append((header_style, column.header))
+ cell_style = get_style(column.style or "")
+ for cell in column.cells:
+ _append((cell_style, cell))
+ if self.show_footer:
+ footer_style = get_style(self.footer_style or "") + get_style(
+ column.footer_style
+ )
+ _append((footer_style, column.footer))
+
+ if any_padding:
+ _Padding = Padding
+ for first, last, (style, renderable) in loop_first_last(raw_cells):
+ yield _Cell(
+ style,
+ _Padding(renderable, get_padding(first, last)),
+ getattr(renderable, "vertical", None) or column.vertical,
+ )
+ else:
+ for (style, renderable) in raw_cells:
+ yield _Cell(
+ style,
+ renderable,
+ getattr(renderable, "vertical", None) or column.vertical,
+ )
+
+ def _get_padding_width(self, column_index: int) -> int:
+ """Get extra width from padding."""
+ _, pad_right, _, pad_left = self.padding
+ if self.collapse_padding:
+ if column_index > 0:
+ pad_left = max(0, pad_left - pad_right)
+ return pad_left + pad_right
+
+ def _measure_column(
+ self,
+ console: "Console",
+ options: "ConsoleOptions",
+ column: Column,
+ ) -> Measurement:
+ """Get the minimum and maximum width of the column."""
+
+ max_width = options.max_width
+ if max_width < 1:
+ return Measurement(0, 0)
+
+ padding_width = self._get_padding_width(column._index)
+
+ if column.width is not None:
+ # Fixed width column
+ return Measurement(
+ column.width + padding_width, column.width + padding_width
+ ).with_maximum(max_width)
+ # Flexible column, we need to measure contents
+ min_widths: List[int] = []
+ max_widths: List[int] = []
+ append_min = min_widths.append
+ append_max = max_widths.append
+ get_render_width = Measurement.get
+ for cell in self._get_cells(console, column._index, column):
+ _min, _max = get_render_width(console, options, cell.renderable)
+ append_min(_min)
+ append_max(_max)
+
+ measurement = Measurement(
+ max(min_widths) if min_widths else 1,
+ max(max_widths) if max_widths else max_width,
+ ).with_maximum(max_width)
+ measurement = measurement.clamp(
+ None if column.min_width is None else column.min_width + padding_width,
+ None if column.max_width is None else column.max_width + padding_width,
+ )
+ return measurement
+
+ def _render(
+ self, console: "Console", options: "ConsoleOptions", widths: List[int]
+ ) -> "RenderResult":
+ table_style = console.get_style(self.style or "")
+
+ border_style = table_style + console.get_style(self.border_style or "")
+ _column_cells = (
+ self._get_cells(console, column_index, column)
+ for column_index, column in enumerate(self.columns)
+ )
+ row_cells: List[Tuple[_Cell, ...]] = list(zip(*_column_cells))
+ _box = (
+ self.box.substitute(
+ options, safe=pick_bool(self.safe_box, console.safe_box)
+ )
+ if self.box
+ else None
+ )
+ _box = _box.get_plain_headed_box() if _box and not self.show_header else _box
+
+ new_line = Segment.line()
+
+ columns = self.columns
+ show_header = self.show_header
+ show_footer = self.show_footer
+ show_edge = self.show_edge
+ show_lines = self.show_lines
+ leading = self.leading
+
+ _Segment = Segment
+ if _box:
+ box_segments = [
+ (
+ _Segment(_box.head_left, border_style),
+ _Segment(_box.head_right, border_style),
+ _Segment(_box.head_vertical, border_style),
+ ),
+ (
+ _Segment(_box.foot_left, border_style),
+ _Segment(_box.foot_right, border_style),
+ _Segment(_box.foot_vertical, border_style),
+ ),
+ (
+ _Segment(_box.mid_left, border_style),
+ _Segment(_box.mid_right, border_style),
+ _Segment(_box.mid_vertical, border_style),
+ ),
+ ]
+ if show_edge:
+ yield _Segment(_box.get_top(widths), border_style)
+ yield new_line
+ else:
+ box_segments = []
+
+ get_row_style = self.get_row_style
+ get_style = console.get_style
+
+ for index, (first, last, row_cell) in enumerate(loop_first_last(row_cells)):
+ header_row = first and show_header
+ footer_row = last and show_footer
+ row = (
+ self.rows[index - show_header]
+ if (not header_row and not footer_row)
+ else None
+ )
+ max_height = 1
+ cells: List[List[List[Segment]]] = []
+ if header_row or footer_row:
+ row_style = Style.null()
+ else:
+ row_style = get_style(
+ get_row_style(console, index - 1 if show_header else index)
+ )
+ for width, cell, column in zip(widths, row_cell, columns):
+ render_options = options.update(
+ width=width,
+ justify=column.justify,
+ no_wrap=column.no_wrap,
+ overflow=column.overflow,
+ height=None,
+ )
+ lines = console.render_lines(
+ cell.renderable,
+ render_options,
+ style=get_style(cell.style) + row_style,
+ )
+ max_height = max(max_height, len(lines))
+ cells.append(lines)
+
+ row_height = max(len(cell) for cell in cells)
+
+ def align_cell(
+ cell: List[List[Segment]],
+ vertical: "VerticalAlignMethod",
+ width: int,
+ style: Style,
+ ) -> List[List[Segment]]:
+ if header_row:
+ vertical = "bottom"
+ elif footer_row:
+ vertical = "top"
+
+ if vertical == "top":
+ return _Segment.align_top(cell, width, row_height, style)
+ elif vertical == "middle":
+ return _Segment.align_middle(cell, width, row_height, style)
+ return _Segment.align_bottom(cell, width, row_height, style)
+
+ cells[:] = [
+ _Segment.set_shape(
+ align_cell(
+ cell,
+ _cell.vertical,
+ width,
+ get_style(_cell.style) + row_style,
+ ),
+ width,
+ max_height,
+ )
+ for width, _cell, cell, column in zip(widths, row_cell, cells, columns)
+ ]
+
+ if _box:
+ if last and show_footer:
+ yield _Segment(
+ _box.get_row(widths, "foot", edge=show_edge), border_style
+ )
+ yield new_line
+ left, right, _divider = box_segments[0 if first else (2 if last else 1)]
+
+ # If the column divider is whitespace also style it with the row background
+ divider = (
+ _divider
+ if _divider.text.strip()
+ else _Segment(
+ _divider.text, row_style.background_style + _divider.style
+ )
+ )
+ for line_no in range(max_height):
+ if show_edge:
+ yield left
+ for last_cell, rendered_cell in loop_last(cells):
+ yield from rendered_cell[line_no]
+ if not last_cell:
+ yield divider
+ if show_edge:
+ yield right
+ yield new_line
+ else:
+ for line_no in range(max_height):
+ for rendered_cell in cells:
+ yield from rendered_cell[line_no]
+ yield new_line
+ if _box and first and show_header:
+ yield _Segment(
+ _box.get_row(widths, "head", edge=show_edge), border_style
+ )
+ yield new_line
+ end_section = row and row.end_section
+ if _box and (show_lines or leading or end_section):
+ if (
+ not last
+ and not (show_footer and index >= len(row_cells) - 2)
+ and not (show_header and header_row)
+ ):
+ if leading:
+ yield _Segment(
+ _box.get_row(widths, "mid", edge=show_edge) * leading,
+ border_style,
+ )
+ else:
+ yield _Segment(
+ _box.get_row(widths, "row", edge=show_edge), border_style
+ )
+ yield new_line
+
+ if _box and show_edge:
+ yield _Segment(_box.get_bottom(widths), border_style)
+ yield new_line
+
+
+if __name__ == "__main__": # pragma: no cover
+ from pip._vendor.rich.console import Console
+ from pip._vendor.rich.highlighter import ReprHighlighter
+ from pip._vendor.rich.table import Table as Table
+
+ from ._timer import timer
+
+ with timer("Table render"):
+ table = Table(
+ title="Star Wars Movies",
+ caption="Rich example table",
+ caption_justify="right",
+ )
+
+ table.add_column(
+ "Released", header_style="bright_cyan", style="cyan", no_wrap=True
+ )
+ table.add_column("Title", style="magenta")
+ table.add_column("Box Office", justify="right", style="green")
+
+ table.add_row(
+ "Dec 20, 2019",
+ "Star Wars: The Rise of Skywalker",
+ "$952,110,690",
+ )
+ table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
+ table.add_row(
+ "Dec 15, 2017",
+ "Star Wars Ep. V111: The Last Jedi",
+ "$1,332,539,889",
+ style="on black",
+ end_section=True,
+ )
+ table.add_row(
+ "Dec 16, 2016",
+ "Rogue One: A Star Wars Story",
+ "$1,332,439,889",
+ )
+
+ def header(text: str) -> None:
+ console.print()
+ console.rule(highlight(text))
+ console.print()
+
+ console = Console()
+ highlight = ReprHighlighter()
+ header("Example Table")
+ console.print(table, justify="center")
+
+ table.expand = True
+ header("expand=True")
+ console.print(table)
+
+ table.width = 50
+ header("width=50")
+
+ console.print(table, justify="center")
+
+ table.width = None
+ table.expand = False
+ table.row_styles = ["dim", "none"]
+ header("row_styles=['dim', 'none']")
+
+ console.print(table, justify="center")
+
+ table.width = None
+ table.expand = False
+ table.row_styles = ["dim", "none"]
+ table.leading = 1
+ header("leading=1, row_styles=['dim', 'none']")
+ console.print(table, justify="center")
+
+ table.width = None
+ table.expand = False
+ table.row_styles = ["dim", "none"]
+ table.show_lines = True
+ table.leading = 0
+ header("show_lines=True, row_styles=['dim', 'none']")
+ console.print(table, justify="center")
diff --git a/src/pip/_vendor/rich/terminal_theme.py b/src/pip/_vendor/rich/terminal_theme.py
new file mode 100644
index 000000000..565e9d960
--- /dev/null
+++ b/src/pip/_vendor/rich/terminal_theme.py
@@ -0,0 +1,153 @@
+from typing import List, Optional, Tuple
+
+from .color_triplet import ColorTriplet
+from .palette import Palette
+
+_ColorTuple = Tuple[int, int, int]
+
+
+class TerminalTheme:
+ """A color theme used when exporting console content.
+
+ Args:
+ background (Tuple[int, int, int]): The background color.
+ foreground (Tuple[int, int, int]): The foreground (text) color.
+ normal (List[Tuple[int, int, int]]): A list of 8 normal intensity colors.
+ bright (List[Tuple[int, int, int]], optional): A list of 8 bright colors, or None
+ to repeat normal intensity. Defaults to None.
+ """
+
+ def __init__(
+ self,
+ background: _ColorTuple,
+ foreground: _ColorTuple,
+ normal: List[_ColorTuple],
+ bright: Optional[List[_ColorTuple]] = None,
+ ) -> None:
+ self.background_color = ColorTriplet(*background)
+ self.foreground_color = ColorTriplet(*foreground)
+ self.ansi_colors = Palette(normal + (bright or normal))
+
+
+DEFAULT_TERMINAL_THEME = TerminalTheme(
+ (255, 255, 255),
+ (0, 0, 0),
+ [
+ (0, 0, 0),
+ (128, 0, 0),
+ (0, 128, 0),
+ (128, 128, 0),
+ (0, 0, 128),
+ (128, 0, 128),
+ (0, 128, 128),
+ (192, 192, 192),
+ ],
+ [
+ (128, 128, 128),
+ (255, 0, 0),
+ (0, 255, 0),
+ (255, 255, 0),
+ (0, 0, 255),
+ (255, 0, 255),
+ (0, 255, 255),
+ (255, 255, 255),
+ ],
+)
+
+MONOKAI = TerminalTheme(
+ (12, 12, 12),
+ (217, 217, 217),
+ [
+ (26, 26, 26),
+ (244, 0, 95),
+ (152, 224, 36),
+ (253, 151, 31),
+ (157, 101, 255),
+ (244, 0, 95),
+ (88, 209, 235),
+ (196, 197, 181),
+ (98, 94, 76),
+ ],
+ [
+ (244, 0, 95),
+ (152, 224, 36),
+ (224, 213, 97),
+ (157, 101, 255),
+ (244, 0, 95),
+ (88, 209, 235),
+ (246, 246, 239),
+ ],
+)
+DIMMED_MONOKAI = TerminalTheme(
+ (25, 25, 25),
+ (185, 188, 186),
+ [
+ (58, 61, 67),
+ (190, 63, 72),
+ (135, 154, 59),
+ (197, 166, 53),
+ (79, 118, 161),
+ (133, 92, 141),
+ (87, 143, 164),
+ (185, 188, 186),
+ (136, 137, 135),
+ ],
+ [
+ (251, 0, 31),
+ (15, 114, 47),
+ (196, 112, 51),
+ (24, 109, 227),
+ (251, 0, 103),
+ (46, 112, 109),
+ (253, 255, 185),
+ ],
+)
+NIGHT_OWLISH = TerminalTheme(
+ (255, 255, 255),
+ (64, 63, 83),
+ [
+ (1, 22, 39),
+ (211, 66, 62),
+ (42, 162, 152),
+ (218, 170, 1),
+ (72, 118, 214),
+ (64, 63, 83),
+ (8, 145, 106),
+ (122, 129, 129),
+ (122, 129, 129),
+ ],
+ [
+ (247, 110, 110),
+ (73, 208, 197),
+ (218, 194, 107),
+ (92, 167, 228),
+ (105, 112, 152),
+ (0, 201, 144),
+ (152, 159, 177),
+ ],
+)
+
+SVG_EXPORT_THEME = TerminalTheme(
+ (41, 41, 41),
+ (197, 200, 198),
+ [
+ (75, 78, 85),
+ (204, 85, 90),
+ (152, 168, 75),
+ (208, 179, 68),
+ (96, 138, 177),
+ (152, 114, 159),
+ (104, 160, 179),
+ (197, 200, 198),
+ (154, 155, 153),
+ ],
+ [
+ (255, 38, 39),
+ (0, 130, 61),
+ (208, 132, 66),
+ (25, 132, 233),
+ (255, 44, 122),
+ (57, 130, 128),
+ (253, 253, 197),
+ ],
+)
diff --git a/src/pip/_vendor/rich/text.py b/src/pip/_vendor/rich/text.py
new file mode 100644
index 000000000..b14055aa7
--- /dev/null
+++ b/src/pip/_vendor/rich/text.py
@@ -0,0 +1,1311 @@
+import re
+from functools import partial, reduce
+from math import gcd
+from operator import itemgetter
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Dict,
+ Iterable,
+ List,
+ NamedTuple,
+ Optional,
+ Tuple,
+ Union,
+)
+
+from ._loop import loop_last
+from ._pick import pick_bool
+from ._wrap import divide_line
+from .align import AlignMethod
+from .cells import cell_len, set_cell_size
+from .containers import Lines
+from .control import strip_control_codes
+from .emoji import EmojiVariant
+from .jupyter import JupyterMixin
+from .measure import Measurement
+from .segment import Segment
+from .style import Style, StyleType
+
+if TYPE_CHECKING: # pragma: no cover
+ from .console import Console, ConsoleOptions, JustifyMethod, OverflowMethod
+
+DEFAULT_JUSTIFY: "JustifyMethod" = "default"
+DEFAULT_OVERFLOW: "OverflowMethod" = "fold"
+
+
+_re_whitespace = re.compile(r"\s+$")
+
+TextType = Union[str, "Text"]
+
+GetStyleCallable = Callable[[str], Optional[StyleType]]
+
+
+class Span(NamedTuple):
+ """A marked up region in some text."""
+
+ start: int
+ """Span start index."""
+ end: int
+ """Span end index."""
+ style: Union[str, Style]
+ """Style associated with the span."""
+
+ def __repr__(self) -> str:
+ return (
+ f"Span({self.start}, {self.end}, {self.style!r})"
+ if (isinstance(self.style, Style) and self.style._meta)
+ else f"Span({self.start}, {self.end}, {repr(self.style)})"
+ )
+
+ def __bool__(self) -> bool:
+ return self.end > self.start
+
+ def split(self, offset: int) -> Tuple["Span", Optional["Span"]]:
+ """Split a span in to 2 from a given offset."""
+
+ if offset < self.start:
+ return self, None
+ if offset >= self.end:
+ return self, None
+
+ start, end, style = self
+ span1 = Span(start, min(end, offset), style)
+ span2 = Span(span1.end, end, style)
+ return span1, span2
+
+ def move(self, offset: int) -> "Span":
+ """Move start and end by a given offset.
+
+ Args:
+ offset (int): Number of characters to add to start and end.
+
+ Returns:
+ TextSpan: A new TextSpan with adjusted position.
+ """
+ start, end, style = self
+ return Span(start + offset, end + offset, style)
+
+ def right_crop(self, offset: int) -> "Span":
+ """Crop the span at the given offset.
+
+ Args:
+ offset (int): A value between start and end.
+
+ Returns:
+ Span: A new (possibly smaller) span.
+ """
+ start, end, style = self
+ if offset >= end:
+ return self
+ return Span(start, min(offset, end), style)
+
+
+class Text(JupyterMixin):
+ """Text with color / style.
+
+ Args:
+ text (str, optional): Default unstyled text. Defaults to "".
+ style (Union[str, Style], optional): Base style for text. Defaults to "".
+ justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None.
+ overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None.
+ no_wrap (bool, optional): Disable text wrapping, or None for default. Defaults to None.
+ end (str, optional): Character to end text with. Defaults to "\\\\n".
+ tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to 8.
+ spans (List[Span], optional). A list of predefined style spans. Defaults to None.
+ """
+
+ __slots__ = [
+ "_text",
+ "style",
+ "justify",
+ "overflow",
+ "no_wrap",
+ "end",
+ "tab_size",
+ "_spans",
+ "_length",
+ ]
+
+ def __init__(
+ self,
+ text: str = "",
+ style: Union[str, Style] = "",
+ *,
+ justify: Optional["JustifyMethod"] = None,
+ overflow: Optional["OverflowMethod"] = None,
+ no_wrap: Optional[bool] = None,
+ end: str = "\n",
+ tab_size: Optional[int] = 8,
+ spans: Optional[List[Span]] = None,
+ ) -> None:
+ sanitized_text = strip_control_codes(text)
+ self._text = [sanitized_text]
+ self.style = style
+ self.justify: Optional["JustifyMethod"] = justify
+ self.overflow: Optional["OverflowMethod"] = overflow
+ self.no_wrap = no_wrap
+ self.end = end
+ self.tab_size = tab_size
+ self._spans: List[Span] = spans or []
+ self._length: int = len(sanitized_text)
+
+ def __len__(self) -> int:
+ return self._length
+
+ def __bool__(self) -> bool:
+ return bool(self._length)
+
+ def __str__(self) -> str:
+ return self.plain
+
+ def __repr__(self) -> str:
+ return f"<text {self.plain!r} {self._spans!r}>"
+
+ def __add__(self, other: Any) -> "Text":
+ if isinstance(other, (str, Text)):
+ result = self.copy()
+ result.append(other)
+ return result
+ return NotImplemented
+
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, Text):
+ return NotImplemented
+ return self.plain == other.plain and self._spans == other._spans
+
+ def __contains__(self, other: object) -> bool:
+ if isinstance(other, str):
+ return other in self.plain
+ elif isinstance(other, Text):
+ return other.plain in self.plain
+ return False
+
+ def __getitem__(self, slice: Union[int, slice]) -> "Text":
+ def get_text_at(offset: int) -> "Text":
+ _Span = Span
+ text = Text(
+ self.plain[offset],
+ spans=[
+ _Span(0, 1, style)
+ for start, end, style in self._spans
+ if end > offset >= start
+ ],
+ end="",
+ )
+ return text
+
+ if isinstance(slice, int):
+ return get_text_at(slice)
+ else:
+ start, stop, step = slice.indices(len(self.plain))
+ if step == 1:
+ lines = self.divide([start, stop])
+ return lines[1]
+ else:
+ # This would be a bit of work to implement efficiently
+ # For now, its not required
+ raise TypeError("slices with step!=1 are not supported")
+
+ @property
+ def cell_len(self) -> int:
+ """Get the number of cells required to render this text."""
+ return cell_len(self.plain)
+
+ @property
+ def markup(self) -> str:
+ """Get console markup to render this Text.
+
+ Returns:
+ str: A string potentially creating markup tags.
+ """
+ from .markup import escape
+
+ output: List[str] = []
+
+ plain = self.plain
+ markup_spans = [
+ (0, False, self.style),
+ *((span.start, False, span.style) for span in self._spans),
+ *((span.end, True, span.style) for span in self._spans),
+ (len(plain), True, self.style),
+ ]
+ markup_spans.sort(key=itemgetter(0, 1))
+ position = 0
+ append = output.append
+ for offset, closing, style in markup_spans:
+ if offset > position:
+ append(escape(plain[position:offset]))
+ position = offset
+ if style:
+ append(f"[/{style}]" if closing else f"[{style}]")
+ markup = "".join(output)
+ return markup
+
+ @classmethod
+ def from_markup(
+ cls,
+ text: str,
+ *,
+ style: Union[str, Style] = "",
+ emoji: bool = True,
+ emoji_variant: Optional[EmojiVariant] = None,
+ justify: Optional["JustifyMethod"] = None,
+ overflow: Optional["OverflowMethod"] = None,
+ end: str = "\n",
+ ) -> "Text":
+ """Create Text instance from markup.
+
+ Args:
+ text (str): A string containing console markup.
+ emoji (bool, optional): Also render emoji code. Defaults to True.
+ justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None.
+ overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None.
+ end (str, optional): Character to end text with. Defaults to "\\\\n".
+
+ Returns:
+ Text: A Text instance with markup rendered.
+ """
+ from .markup import render
+
+ rendered_text = render(text, style, emoji=emoji, emoji_variant=emoji_variant)
+ rendered_text.justify = justify
+ rendered_text.overflow = overflow
+ rendered_text.end = end
+ return rendered_text
+
+ @classmethod
+ def from_ansi(
+ cls,
+ text: str,
+ *,
+ style: Union[str, Style] = "",
+ justify: Optional["JustifyMethod"] = None,
+ overflow: Optional["OverflowMethod"] = None,
+ no_wrap: Optional[bool] = None,
+ end: str = "\n",
+ tab_size: Optional[int] = 8,
+ ) -> "Text":
+ """Create a Text object from a string containing ANSI escape codes.
+
+ Args:
+ text (str): A string containing escape codes.
+ style (Union[str, Style], optional): Base style for text. Defaults to "".
+ justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None.
+ overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None.
+ no_wrap (bool, optional): Disable text wrapping, or None for default. Defaults to None.
+ end (str, optional): Character to end text with. Defaults to "\\\\n".
+ tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to 8.
+ """
+ from .ansi import AnsiDecoder
+
+ joiner = Text(
+ "\n",
+ justify=justify,
+ overflow=overflow,
+ no_wrap=no_wrap,
+ end=end,
+ tab_size=tab_size,
+ style=style,
+ )
+ decoder = AnsiDecoder()
+ result = joiner.join(line for line in decoder.decode(text))
+ return result
+
+ @classmethod
+ def styled(
+ cls,
+ text: str,
+ style: StyleType = "",
+ *,
+ justify: Optional["JustifyMethod"] = None,
+ overflow: Optional["OverflowMethod"] = None,
+ ) -> "Text":
+ """Construct a Text instance with a pre-applied styled. A style applied in this way won't be used
+ to pad the text when it is justified.
+
+ Args:
+ text (str): A string containing console markup.
+ style (Union[str, Style]): Style to apply to the text. Defaults to "".
+ justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None.
+ overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None.
+
+ Returns:
+ Text: A text instance with a style applied to the entire string.
+ """
+ styled_text = cls(text, justify=justify, overflow=overflow)
+ styled_text.stylize(style)
+ return styled_text
+
+ @classmethod
+ def assemble(
+ cls,
+ *parts: Union[str, "Text", Tuple[str, StyleType]],
+ style: Union[str, Style] = "",
+ justify: Optional["JustifyMethod"] = None,
+ overflow: Optional["OverflowMethod"] = None,
+ no_wrap: Optional[bool] = None,
+ end: str = "\n",
+ tab_size: int = 8,
+ meta: Optional[Dict[str, Any]] = None,
+ ) -> "Text":
+ """Construct a text instance by combining a sequence of strings with optional styles.
+ The positional arguments should be either strings, or a tuple of string + style.
+
+ Args:
+ style (Union[str, Style], optional): Base style for text. Defaults to "".
+ justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None.
+ overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None.
+ end (str, optional): Character to end text with. Defaults to "\\\\n".
+ tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to 8.
+ meta (Dict[str, Any], optional). Meta data to apply to text, or None for no meta data. Default to None
+
+ Returns:
+ Text: A new text instance.
+ """
+ text = cls(
+ style=style,
+ justify=justify,
+ overflow=overflow,
+ no_wrap=no_wrap,
+ end=end,
+ tab_size=tab_size,
+ )
+ append = text.append
+ _Text = Text
+ for part in parts:
+ if isinstance(part, (_Text, str)):
+ append(part)
+ else:
+ append(*part)
+ if meta:
+ text.apply_meta(meta)
+ return text
+
+ @property
+ def plain(self) -> str:
+ """Get the text as a single string."""
+ if len(self._text) != 1:
+ self._text[:] = ["".join(self._text)]
+ return self._text[0]
+
+ @plain.setter
+ def plain(self, new_text: str) -> None:
+ """Set the text to a new value."""
+ if new_text != self.plain:
+ sanitized_text = strip_control_codes(new_text)
+ self._text[:] = [sanitized_text]
+ old_length = self._length
+ self._length = len(sanitized_text)
+ if old_length > self._length:
+ self._trim_spans()
+
+ @property
+ def spans(self) -> List[Span]:
+ """Get a reference to the internal list of spans."""
+ return self._spans
+
+ @spans.setter
+ def spans(self, spans: List[Span]) -> None:
+ """Set spans."""
+ self._spans = spans[:]
+
+ def blank_copy(self, plain: str = "") -> "Text":
+ """Return a new Text instance with copied meta data (but not the string or spans)."""
+ copy_self = Text(
+ plain,
+ style=self.style,
+ justify=self.justify,
+ overflow=self.overflow,
+ no_wrap=self.no_wrap,
+ end=self.end,
+ tab_size=self.tab_size,
+ )
+ return copy_self
+
+ def copy(self) -> "Text":
+ """Return a copy of this instance."""
+ copy_self = Text(
+ self.plain,
+ style=self.style,
+ justify=self.justify,
+ overflow=self.overflow,
+ no_wrap=self.no_wrap,
+ end=self.end,
+ tab_size=self.tab_size,
+ )
+ copy_self._spans[:] = self._spans
+ return copy_self
+
+ def stylize(
+ self,
+ style: Union[str, Style],
+ start: int = 0,
+ end: Optional[int] = None,
+ ) -> None:
+ """Apply a style to the text, or a portion of the text.
+
+ Args:
+ style (Union[str, Style]): Style instance or style definition to apply.
+ start (int): Start offset (negative indexing is supported). Defaults to 0.
+ end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None.
+ """
+ if style:
+ length = len(self)
+ if start < 0:
+ start = length + start
+ if end is None:
+ end = length
+ if end < 0:
+ end = length + end
+ if start >= length or end <= start:
+ # Span not in text or not valid
+ return
+ self._spans.append(Span(start, min(length, end), style))
+
+ def stylize_before(
+ self,
+ style: Union[str, Style],
+ start: int = 0,
+ end: Optional[int] = None,
+ ) -> None:
+ """Apply a style to the text, or a portion of the text. Styles will be applied before other styles already present.
+
+ Args:
+ style (Union[str, Style]): Style instance or style definition to apply.
+ start (int): Start offset (negative indexing is supported). Defaults to 0.
+ end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None.
+ """
+ if style:
+ length = len(self)
+ if start < 0:
+ start = length + start
+ if end is None:
+ end = length
+ if end < 0:
+ end = length + end
+ if start >= length or end <= start:
+ # Span not in text or not valid
+ return
+ self._spans.insert(0, Span(start, min(length, end), style))
+
+ def apply_meta(
+ self, meta: Dict[str, Any], start: int = 0, end: Optional[int] = None
+ ) -> None:
+ """Apply meta data to the text, or a portion of the text.
+
+ Args:
+ meta (Dict[str, Any]): A dict of meta information.
+ start (int): Start offset (negative indexing is supported). Defaults to 0.
+ end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None.
+
+ """
+ style = Style.from_meta(meta)
+ self.stylize(style, start=start, end=end)
+
+ def on(self, meta: Optional[Dict[str, Any]] = None, **handlers: Any) -> "Text":
+ """Apply event handlers (used by Textual project).
+
+ Example:
+ >>> from rich.text import Text
+ >>> text = Text("hello world")
+ >>> text.on(click="view.toggle('world')")
+
+ Args:
+ meta (Dict[str, Any]): Mapping of meta information.
+ **handlers: Keyword args are prefixed with "@" to defined handlers.
+
+ Returns:
+ Text: Self is returned to method may be chained.
+ """
+ meta = {} if meta is None else meta
+ meta.update({f"@{key}": value for key, value in handlers.items()})
+ self.stylize(Style.from_meta(meta))
+ return self
+
+ def remove_suffix(self, suffix: str) -> None:
+ """Remove a suffix if it exists.
+
+ Args:
+ suffix (str): Suffix to remove.
+ """
+ if self.plain.endswith(suffix):
+ self.right_crop(len(suffix))
+
+ def get_style_at_offset(self, console: "Console", offset: int) -> Style:
+ """Get the style of a character at give offset.
+
+ Args:
+ console (~Console): Console where text will be rendered.
+ offset (int): Offset in to text (negative indexing supported)
+
+ Returns:
+ Style: A Style instance.
+ """
+ # TODO: This is a little inefficient, it is only used by full justify
+ if offset < 0:
+ offset = len(self) + offset
+ get_style = console.get_style
+ style = get_style(self.style).copy()
+ for start, end, span_style in self._spans:
+ if end > offset >= start:
+ style += get_style(span_style, default="")
+ return style
+
+ def highlight_regex(
+ self,
+ re_highlight: str,
+ style: Optional[Union[GetStyleCallable, StyleType]] = None,
+ *,
+ style_prefix: str = "",
+ ) -> int:
+ """Highlight text with a regular expression, where group names are
+ translated to styles.
+
+ Args:
+ re_highlight (str): A regular expression.
+ style (Union[GetStyleCallable, StyleType]): Optional style to apply to whole match, or a callable
+ which accepts the matched text and returns a style. Defaults to None.
+ style_prefix (str, optional): Optional prefix to add to style group names.
+
+ Returns:
+ int: Number of regex matches
+ """
+ count = 0
+ append_span = self._spans.append
+ _Span = Span
+ plain = self.plain
+ for match in re.finditer(re_highlight, plain):
+ get_span = match.span
+ if style:
+ start, end = get_span()
+ match_style = style(plain[start:end]) if callable(style) else style
+ if match_style is not None and end > start:
+ append_span(_Span(start, end, match_style))
+
+ count += 1
+ for name in match.groupdict().keys():
+ start, end = get_span(name)
+ if start != -1 and end > start:
+ append_span(_Span(start, end, f"{style_prefix}{name}"))
+ return count
+
+ def highlight_words(
+ self,
+ words: Iterable[str],
+ style: Union[str, Style],
+ *,
+ case_sensitive: bool = True,
+ ) -> int:
+ """Highlight words with a style.
+
+ Args:
+ words (Iterable[str]): Worlds to highlight.
+ style (Union[str, Style]): Style to apply.
+ case_sensitive (bool, optional): Enable case sensitive matchings. Defaults to True.
+
+ Returns:
+ int: Number of words highlighted.
+ """
+ re_words = "|".join(re.escape(word) for word in words)
+ add_span = self._spans.append
+ count = 0
+ _Span = Span
+ for match in re.finditer(
+ re_words, self.plain, flags=0 if case_sensitive else re.IGNORECASE
+ ):
+ start, end = match.span(0)
+ add_span(_Span(start, end, style))
+ count += 1
+ return count
+
+ def rstrip(self) -> None:
+ """Strip whitespace from end of text."""
+ self.plain = self.plain.rstrip()
+
+ def rstrip_end(self, size: int) -> None:
+ """Remove whitespace beyond a certain width at the end of the text.
+
+ Args:
+ size (int): The desired size of the text.
+ """
+ text_length = len(self)
+ if text_length > size:
+ excess = text_length - size
+ whitespace_match = _re_whitespace.search(self.plain)
+ if whitespace_match is not None:
+ whitespace_count = len(whitespace_match.group(0))
+ self.right_crop(min(whitespace_count, excess))
+
+ def set_length(self, new_length: int) -> None:
+ """Set new length of the text, clipping or padding is required."""
+ length = len(self)
+ if length != new_length:
+ if length < new_length:
+ self.pad_right(new_length - length)
+ else:
+ self.right_crop(length - new_length)
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> Iterable[Segment]:
+ tab_size: int = console.tab_size or self.tab_size or 8
+ justify = self.justify or options.justify or DEFAULT_JUSTIFY
+
+ overflow = self.overflow or options.overflow or DEFAULT_OVERFLOW
+
+ lines = self.wrap(
+ console,
+ options.max_width,
+ justify=justify,
+ overflow=overflow,
+ tab_size=tab_size or 8,
+ no_wrap=pick_bool(self.no_wrap, options.no_wrap, False),
+ )
+ all_lines = Text("\n").join(lines)
+ yield from all_lines.render(console, end=self.end)
+
+ def __rich_measure__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> Measurement:
+ text = self.plain
+ lines = text.splitlines()
+ max_text_width = max(cell_len(line) for line in lines) if lines else 0
+ words = text.split()
+ min_text_width = (
+ max(cell_len(word) for word in words) if words else max_text_width
+ )
+ return Measurement(min_text_width, max_text_width)
+
+ def render(self, console: "Console", end: str = "") -> Iterable["Segment"]:
+ """Render the text as Segments.
+
+ Args:
+ console (Console): Console instance.
+ end (Optional[str], optional): Optional end character.
+
+ Returns:
+ Iterable[Segment]: Result of render that may be written to the console.
+ """
+ _Segment = Segment
+ text = self.plain
+ if not self._spans:
+ yield Segment(text)
+ if end:
+ yield _Segment(end)
+ return
+ get_style = partial(console.get_style, default=Style.null())
+
+ enumerated_spans = list(enumerate(self._spans, 1))
+ style_map = {index: get_style(span.style) for index, span in enumerated_spans}
+ style_map[0] = get_style(self.style)
+
+ spans = [
+ (0, False, 0),
+ *((span.start, False, index) for index, span in enumerated_spans),
+ *((span.end, True, index) for index, span in enumerated_spans),
+ (len(text), True, 0),
+ ]
+ spans.sort(key=itemgetter(0, 1))
+
+ stack: List[int] = []
+ stack_append = stack.append
+ stack_pop = stack.remove
+
+ style_cache: Dict[Tuple[Style, ...], Style] = {}
+ style_cache_get = style_cache.get
+ combine = Style.combine
+
+ def get_current_style() -> Style:
+ """Construct current style from stack."""
+ styles = tuple(style_map[_style_id] for _style_id in sorted(stack))
+ cached_style = style_cache_get(styles)
+ if cached_style is not None:
+ return cached_style
+ current_style = combine(styles)
+ style_cache[styles] = current_style
+ return current_style
+
+ for (offset, leaving, style_id), (next_offset, _, _) in zip(spans, spans[1:]):
+ if leaving:
+ stack_pop(style_id)
+ else:
+ stack_append(style_id)
+ if next_offset > offset:
+ yield _Segment(text[offset:next_offset], get_current_style())
+ if end:
+ yield _Segment(end)
+
+ def join(self, lines: Iterable["Text"]) -> "Text":
+ """Join text together with this instance as the separator.
+
+ Args:
+ lines (Iterable[Text]): An iterable of Text instances to join.
+
+ Returns:
+ Text: A new text instance containing join text.
+ """
+
+ new_text = self.blank_copy()
+
+ def iter_text() -> Iterable["Text"]:
+ if self.plain:
+ for last, line in loop_last(lines):
+ yield line
+ if not last:
+ yield self
+ else:
+ yield from lines
+
+ extend_text = new_text._text.extend
+ append_span = new_text._spans.append
+ extend_spans = new_text._spans.extend
+ offset = 0
+ _Span = Span
+
+ for text in iter_text():
+ extend_text(text._text)
+ if text.style:
+ append_span(_Span(offset, offset + len(text), text.style))
+ extend_spans(
+ _Span(offset + start, offset + end, style)
+ for start, end, style in text._spans
+ )
+ offset += len(text)
+ new_text._length = offset
+ return new_text
+
+ def expand_tabs(self, tab_size: Optional[int] = None) -> None:
+ """Converts tabs to spaces.
+
+ Args:
+ tab_size (int, optional): Size of tabs. Defaults to 8.
+
+ """
+ if "\t" not in self.plain:
+ return
+ pos = 0
+ if tab_size is None:
+ tab_size = self.tab_size
+ assert tab_size is not None
+ result = self.blank_copy()
+ append = result.append
+
+ _style = self.style
+ for line in self.split("\n", include_separator=True):
+ parts = line.split("\t", include_separator=True)
+ for part in parts:
+ if part.plain.endswith("\t"):
+ part._text = [part.plain[:-1] + " "]
+ append(part)
+ pos += len(part)
+ spaces = tab_size - ((pos - 1) % tab_size) - 1
+ if spaces:
+ append(" " * spaces, _style)
+ pos += spaces
+ else:
+ append(part)
+ self._text = [result.plain]
+ self._length = len(self.plain)
+ self._spans[:] = result._spans
+
+ def truncate(
+ self,
+ max_width: int,
+ *,
+ overflow: Optional["OverflowMethod"] = None,
+ pad: bool = False,
+ ) -> None:
+ """Truncate text if it is longer that a given width.
+
+ Args:
+ max_width (int): Maximum number of characters in text.
+ overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None, to use self.overflow.
+ pad (bool, optional): Pad with spaces if the length is less than max_width. Defaults to False.
+ """
+ _overflow = overflow or self.overflow or DEFAULT_OVERFLOW
+ if _overflow != "ignore":
+ length = cell_len(self.plain)
+ if length > max_width:
+ if _overflow == "ellipsis":
+ self.plain = set_cell_size(self.plain, max_width - 1) + "…"
+ else:
+ self.plain = set_cell_size(self.plain, max_width)
+ if pad and length < max_width:
+ spaces = max_width - length
+ self._text = [f"{self.plain}{' ' * spaces}"]
+ self._length = len(self.plain)
+
+ def _trim_spans(self) -> None:
+ """Remove or modify any spans that are over the end of the text."""
+ max_offset = len(self.plain)
+ _Span = Span
+ self._spans[:] = [
+ (
+ span
+ if span.end < max_offset
+ else _Span(span.start, min(max_offset, span.end), span.style)
+ )
+ for span in self._spans
+ if span.start < max_offset
+ ]
+
+ def pad(self, count: int, character: str = " ") -> None:
+ """Pad left and right with a given number of characters.
+
+ Args:
+ count (int): Width of padding.
+ """
+ assert len(character) == 1, "Character must be a string of length 1"
+ if count:
+ pad_characters = character * count
+ self.plain = f"{pad_characters}{self.plain}{pad_characters}"
+ _Span = Span
+ self._spans[:] = [
+ _Span(start + count, end + count, style)
+ for start, end, style in self._spans
+ ]
+
+ def pad_left(self, count: int, character: str = " ") -> None:
+ """Pad the left with a given character.
+
+ Args:
+ count (int): Number of characters to pad.
+ character (str, optional): Character to pad with. Defaults to " ".
+ """
+ assert len(character) == 1, "Character must be a string of length 1"
+ if count:
+ self.plain = f"{character * count}{self.plain}"
+ _Span = Span
+ self._spans[:] = [
+ _Span(start + count, end + count, style)
+ for start, end, style in self._spans
+ ]
+
+ def pad_right(self, count: int, character: str = " ") -> None:
+ """Pad the right with a given character.
+
+ Args:
+ count (int): Number of characters to pad.
+ character (str, optional): Character to pad with. Defaults to " ".
+ """
+ assert len(character) == 1, "Character must be a string of length 1"
+ if count:
+ self.plain = f"{self.plain}{character * count}"
+
+ def align(self, align: AlignMethod, width: int, character: str = " ") -> None:
+ """Align text to a given width.
+
+ Args:
+ align (AlignMethod): One of "left", "center", or "right".
+ width (int): Desired width.
+ character (str, optional): Character to pad with. Defaults to " ".
+ """
+ self.truncate(width)
+ excess_space = width - cell_len(self.plain)
+ if excess_space:
+ if align == "left":
+ self.pad_right(excess_space, character)
+ elif align == "center":
+ left = excess_space // 2
+ self.pad_left(left, character)
+ self.pad_right(excess_space - left, character)
+ else:
+ self.pad_left(excess_space, character)
+
+ def append(
+ self, text: Union["Text", str], style: Optional[Union[str, "Style"]] = None
+ ) -> "Text":
+ """Add text with an optional style.
+
+ Args:
+ text (Union[Text, str]): A str or Text to append.
+ style (str, optional): A style name. Defaults to None.
+
+ Returns:
+ Text: Returns self for chaining.
+ """
+
+ if not isinstance(text, (str, Text)):
+ raise TypeError("Only str or Text can be appended to Text")
+
+ if len(text):
+ if isinstance(text, str):
+ sanitized_text = strip_control_codes(text)
+ self._text.append(sanitized_text)
+ offset = len(self)
+ text_length = len(sanitized_text)
+ if style is not None:
+ self._spans.append(Span(offset, offset + text_length, style))
+ self._length += text_length
+ elif isinstance(text, Text):
+ _Span = Span
+ if style is not None:
+ raise ValueError(
+ "style must not be set when appending Text instance"
+ )
+ text_length = self._length
+ if text.style is not None:
+ self._spans.append(
+ _Span(text_length, text_length + len(text), text.style)
+ )
+ self._text.append(text.plain)
+ self._spans.extend(
+ _Span(start + text_length, end + text_length, style)
+ for start, end, style in text._spans
+ )
+ self._length += len(text)
+ return self
+
+ def append_text(self, text: "Text") -> "Text":
+ """Append another Text instance. This method is more performant that Text.append, but
+ only works for Text.
+
+ Returns:
+ Text: Returns self for chaining.
+ """
+ _Span = Span
+ text_length = self._length
+ if text.style is not None:
+ self._spans.append(_Span(text_length, text_length + len(text), text.style))
+ self._text.append(text.plain)
+ self._spans.extend(
+ _Span(start + text_length, end + text_length, style)
+ for start, end, style in text._spans
+ )
+ self._length += len(text)
+ return self
+
+ def append_tokens(
+ self, tokens: Iterable[Tuple[str, Optional[StyleType]]]
+ ) -> "Text":
+ """Append iterable of str and style. Style may be a Style instance or a str style definition.
+
+ Args:
+ pairs (Iterable[Tuple[str, Optional[StyleType]]]): An iterable of tuples containing str content and style.
+
+ Returns:
+ Text: Returns self for chaining.
+ """
+ append_text = self._text.append
+ append_span = self._spans.append
+ _Span = Span
+ offset = len(self)
+ for content, style in tokens:
+ append_text(content)
+ if style is not None:
+ append_span(_Span(offset, offset + len(content), style))
+ offset += len(content)
+ self._length = offset
+ return self
+
+ def copy_styles(self, text: "Text") -> None:
+ """Copy styles from another Text instance.
+
+ Args:
+ text (Text): A Text instance to copy styles from, must be the same length.
+ """
+ self._spans.extend(text._spans)
+
+ def split(
+ self,
+ separator: str = "\n",
+ *,
+ include_separator: bool = False,
+ allow_blank: bool = False,
+ ) -> Lines:
+ """Split rich text in to lines, preserving styles.
+
+ Args:
+ separator (str, optional): String to split on. Defaults to "\\\\n".
+ include_separator (bool, optional): Include the separator in the lines. Defaults to False.
+ allow_blank (bool, optional): Return a blank line if the text ends with a separator. Defaults to False.
+
+ Returns:
+ List[RichText]: A list of rich text, one per line of the original.
+ """
+ assert separator, "separator must not be empty"
+
+ text = self.plain
+ if separator not in text:
+ return Lines([self.copy()])
+
+ if include_separator:
+ lines = self.divide(
+ match.end() for match in re.finditer(re.escape(separator), text)
+ )
+ else:
+
+ def flatten_spans() -> Iterable[int]:
+ for match in re.finditer(re.escape(separator), text):
+ start, end = match.span()
+ yield start
+ yield end
+
+ lines = Lines(
+ line for line in self.divide(flatten_spans()) if line.plain != separator
+ )
+
+ if not allow_blank and text.endswith(separator):
+ lines.pop()
+
+ return lines
+
+ def divide(self, offsets: Iterable[int]) -> Lines:
+ """Divide text in to a number of lines at given offsets.
+
+ Args:
+ offsets (Iterable[int]): Offsets used to divide text.
+
+ Returns:
+ Lines: New RichText instances between offsets.
+ """
+ _offsets = list(offsets)
+
+ if not _offsets:
+ return Lines([self.copy()])
+
+ text = self.plain
+ text_length = len(text)
+ divide_offsets = [0, *_offsets, text_length]
+ line_ranges = list(zip(divide_offsets, divide_offsets[1:]))
+
+ style = self.style
+ justify = self.justify
+ overflow = self.overflow
+ _Text = Text
+ new_lines = Lines(
+ _Text(
+ text[start:end],
+ style=style,
+ justify=justify,
+ overflow=overflow,
+ )
+ for start, end in line_ranges
+ )
+ if not self._spans:
+ return new_lines
+
+ _line_appends = [line._spans.append for line in new_lines._lines]
+ line_count = len(line_ranges)
+ _Span = Span
+
+ for span_start, span_end, style in self._spans:
+
+ lower_bound = 0
+ upper_bound = line_count
+ start_line_no = (lower_bound + upper_bound) // 2
+
+ while True:
+ line_start, line_end = line_ranges[start_line_no]
+ if span_start < line_start:
+ upper_bound = start_line_no - 1
+ elif span_start > line_end:
+ lower_bound = start_line_no + 1
+ else:
+ break
+ start_line_no = (lower_bound + upper_bound) // 2
+
+ if span_end < line_end:
+ end_line_no = start_line_no
+ else:
+ end_line_no = lower_bound = start_line_no
+ upper_bound = line_count
+
+ while True:
+ line_start, line_end = line_ranges[end_line_no]
+ if span_end < line_start:
+ upper_bound = end_line_no - 1
+ elif span_end > line_end:
+ lower_bound = end_line_no + 1
+ else:
+ break
+ end_line_no = (lower_bound + upper_bound) // 2
+
+ for line_no in range(start_line_no, end_line_no + 1):
+ line_start, line_end = line_ranges[line_no]
+ new_start = max(0, span_start - line_start)
+ new_end = min(span_end - line_start, line_end - line_start)
+ if new_end > new_start:
+ _line_appends[line_no](_Span(new_start, new_end, style))
+
+ return new_lines
+
+ def right_crop(self, amount: int = 1) -> None:
+ """Remove a number of characters from the end of the text."""
+ max_offset = len(self.plain) - amount
+ _Span = Span
+ self._spans[:] = [
+ (
+ span
+ if span.end < max_offset
+ else _Span(span.start, min(max_offset, span.end), span.style)
+ )
+ for span in self._spans
+ if span.start < max_offset
+ ]
+ self._text = [self.plain[:-amount]]
+ self._length -= amount
+
+ def wrap(
+ self,
+ console: "Console",
+ width: int,
+ *,
+ justify: Optional["JustifyMethod"] = None,
+ overflow: Optional["OverflowMethod"] = None,
+ tab_size: int = 8,
+ no_wrap: Optional[bool] = None,
+ ) -> Lines:
+ """Word wrap the text.
+
+ Args:
+ console (Console): Console instance.
+ width (int): Number of characters per line.
+ emoji (bool, optional): Also render emoji code. Defaults to True.
+ justify (str, optional): Justify method: "default", "left", "center", "full", "right". Defaults to "default".
+ overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None.
+ tab_size (int, optional): Default tab size. Defaults to 8.
+ no_wrap (bool, optional): Disable wrapping, Defaults to False.
+
+ Returns:
+ Lines: Number of lines.
+ """
+ wrap_justify = justify or self.justify or DEFAULT_JUSTIFY
+ wrap_overflow = overflow or self.overflow or DEFAULT_OVERFLOW
+
+ no_wrap = pick_bool(no_wrap, self.no_wrap, False) or overflow == "ignore"
+
+ lines = Lines()
+ for line in self.split(allow_blank=True):
+ if "\t" in line:
+ line.expand_tabs(tab_size)
+ if no_wrap:
+ new_lines = Lines([line])
+ else:
+ offsets = divide_line(str(line), width, fold=wrap_overflow == "fold")
+ new_lines = line.divide(offsets)
+ for line in new_lines:
+ line.rstrip_end(width)
+ if wrap_justify:
+ new_lines.justify(
+ console, width, justify=wrap_justify, overflow=wrap_overflow
+ )
+ for line in new_lines:
+ line.truncate(width, overflow=wrap_overflow)
+ lines.extend(new_lines)
+ return lines
+
+ def fit(self, width: int) -> Lines:
+ """Fit the text in to given width by chopping in to lines.
+
+ Args:
+ width (int): Maximum characters in a line.
+
+ Returns:
+ Lines: List of lines.
+ """
+ lines: Lines = Lines()
+ append = lines.append
+ for line in self.split():
+ line.set_length(width)
+ append(line)
+ return lines
+
+ def detect_indentation(self) -> int:
+ """Auto-detect indentation of code.
+
+ Returns:
+ int: Number of spaces used to indent code.
+ """
+
+ _indentations = {
+ len(match.group(1))
+ for match in re.finditer(r"^( *)(.*)$", self.plain, flags=re.MULTILINE)
+ }
+
+ try:
+ indentation = (
+ reduce(gcd, [indent for indent in _indentations if not indent % 2]) or 1
+ )
+ except TypeError:
+ indentation = 1
+
+ return indentation
+
+ def with_indent_guides(
+ self,
+ indent_size: Optional[int] = None,
+ *,
+ character: str = "│",
+ style: StyleType = "dim green",
+ ) -> "Text":
+ """Adds indent guide lines to text.
+
+ Args:
+ indent_size (Optional[int]): Size of indentation, or None to auto detect. Defaults to None.
+ character (str, optional): Character to use for indentation. Defaults to "│".
+ style (Union[Style, str], optional): Style of indent guides.
+
+ Returns:
+ Text: New text with indentation guides.
+ """
+
+ _indent_size = self.detect_indentation() if indent_size is None else indent_size
+
+ text = self.copy()
+ text.expand_tabs()
+ indent_line = f"{character}{' ' * (_indent_size - 1)}"
+
+ re_indent = re.compile(r"^( *)(.*)$")
+ new_lines: List[Text] = []
+ add_line = new_lines.append
+ blank_lines = 0
+ for line in text.split(allow_blank=True):
+ match = re_indent.match(line.plain)
+ if not match or not match.group(2):
+ blank_lines += 1
+ continue
+ indent = match.group(1)
+ full_indents, remaining_space = divmod(len(indent), _indent_size)
+ new_indent = f"{indent_line * full_indents}{' ' * remaining_space}"
+ line.plain = new_indent + line.plain[len(new_indent) :]
+ line.stylize(style, 0, len(new_indent))
+ if blank_lines:
+ new_lines.extend([Text(new_indent, style=style)] * blank_lines)
+ blank_lines = 0
+ add_line(line)
+ if blank_lines:
+ new_lines.extend([Text("", style=style)] * blank_lines)
+
+ new_text = text.blank_copy("\n").join(new_lines)
+ return new_text
+
+
+if __name__ == "__main__": # pragma: no cover
+ from pip._vendor.rich.console import Console
+
+ text = Text(
+ """\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n"""
+ )
+ text.highlight_words(["Lorem"], "bold")
+ text.highlight_words(["ipsum"], "italic")
+
+ console = Console()
+
+ console.rule("justify='left'")
+ console.print(text, style="red")
+ console.print()
+
+ console.rule("justify='center'")
+ console.print(text, style="green", justify="center")
+ console.print()
+
+ console.rule("justify='right'")
+ console.print(text, style="blue", justify="right")
+ console.print()
+
+ console.rule("justify='full'")
+ console.print(text, style="magenta", justify="full")
+ console.print()
diff --git a/src/pip/_vendor/rich/theme.py b/src/pip/_vendor/rich/theme.py
new file mode 100644
index 000000000..bfb3c7f82
--- /dev/null
+++ b/src/pip/_vendor/rich/theme.py
@@ -0,0 +1,112 @@
+import configparser
+from typing import Dict, List, IO, Mapping, Optional
+
+from .default_styles import DEFAULT_STYLES
+from .style import Style, StyleType
+
+
+class Theme:
+ """A container for style information, used by :class:`~rich.console.Console`.
+
+ Args:
+ styles (Dict[str, Style], optional): A mapping of style names on to styles. Defaults to None for a theme with no styles.
+ inherit (bool, optional): Inherit default styles. Defaults to True.
+ """
+
+ styles: Dict[str, Style]
+
+ def __init__(
+ self, styles: Optional[Mapping[str, StyleType]] = None, inherit: bool = True
+ ):
+ self.styles = DEFAULT_STYLES.copy() if inherit else {}
+ if styles is not None:
+ self.styles.update(
+ {
+ name: style if isinstance(style, Style) else Style.parse(style)
+ for name, style in styles.items()
+ }
+ )
+
+ @property
+ def config(self) -> str:
+ """Get contents of a config file for this theme."""
+ config = "[styles]\n" + "\n".join(
+ f"{name} = {style}" for name, style in sorted(self.styles.items())
+ )
+ return config
+
+ @classmethod
+ def from_file(
+ cls, config_file: IO[str], source: Optional[str] = None, inherit: bool = True
+ ) -> "Theme":
+ """Load a theme from a text mode file.
+
+ Args:
+ config_file (IO[str]): An open conf file.
+ source (str, optional): The filename of the open file. Defaults to None.
+ inherit (bool, optional): Inherit default styles. Defaults to True.
+
+ Returns:
+ Theme: A New theme instance.
+ """
+ config = configparser.ConfigParser()
+ config.read_file(config_file, source=source)
+ styles = {name: Style.parse(value) for name, value in config.items("styles")}
+ theme = Theme(styles, inherit=inherit)
+ return theme
+
+ @classmethod
+ def read(cls, path: str, inherit: bool = True) -> "Theme":
+ """Read a theme from a path.
+
+ Args:
+ path (str): Path to a config file readable by Python configparser module.
+ inherit (bool, optional): Inherit default styles. Defaults to True.
+
+ Returns:
+ Theme: A new theme instance.
+ """
+ with open(path, "rt") as config_file:
+ return cls.from_file(config_file, source=path, inherit=inherit)
+
+
+class ThemeStackError(Exception):
+ """Base exception for errors related to the theme stack."""
+
+
+class ThemeStack:
+ """A stack of themes.
+
+ Args:
+ theme (Theme): A theme instance
+ """
+
+ def __init__(self, theme: Theme) -> None:
+ self._entries: List[Dict[str, Style]] = [theme.styles]
+ self.get = self._entries[-1].get
+
+ def push_theme(self, theme: Theme, inherit: bool = True) -> None:
+ """Push a theme on the top of the stack.
+
+ Args:
+ theme (Theme): A Theme instance.
+ inherit (boolean, optional): Inherit styles from current top of stack.
+ """
+ styles: Dict[str, Style]
+ styles = (
+ {**self._entries[-1], **theme.styles} if inherit else theme.styles.copy()
+ )
+ self._entries.append(styles)
+ self.get = self._entries[-1].get
+
+ def pop_theme(self) -> None:
+ """Pop (and discard) the top-most theme."""
+ if len(self._entries) == 1:
+ raise ThemeStackError("Unable to pop base theme")
+ self._entries.pop()
+ self.get = self._entries[-1].get
+
+
+if __name__ == "__main__": # pragma: no cover
+ theme = Theme()
+ print(theme.config)
diff --git a/src/pip/_vendor/rich/themes.py b/src/pip/_vendor/rich/themes.py
new file mode 100644
index 000000000..bf6db104a
--- /dev/null
+++ b/src/pip/_vendor/rich/themes.py
@@ -0,0 +1,5 @@
+from .default_styles import DEFAULT_STYLES
+from .theme import Theme
+
+
+DEFAULT = Theme(DEFAULT_STYLES)
diff --git a/src/pip/_vendor/rich/traceback.py b/src/pip/_vendor/rich/traceback.py
new file mode 100644
index 000000000..1f481298f
--- /dev/null
+++ b/src/pip/_vendor/rich/traceback.py
@@ -0,0 +1,677 @@
+from __future__ import absolute_import
+
+import os
+import platform
+import sys
+from dataclasses import dataclass, field
+from traceback import walk_tb
+from types import ModuleType, TracebackType
+from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Type, Union
+
+from pip._vendor.pygments.lexers import guess_lexer_for_filename
+from pip._vendor.pygments.token import Comment, Keyword, Name, Number, Operator, String
+from pip._vendor.pygments.token import Text as TextToken
+from pip._vendor.pygments.token import Token
+from pip._vendor.pygments.util import ClassNotFound
+
+from . import pretty
+from ._loop import loop_last
+from .columns import Columns
+from .console import Console, ConsoleOptions, ConsoleRenderable, RenderResult, group
+from .constrain import Constrain
+from .highlighter import RegexHighlighter, ReprHighlighter
+from .panel import Panel
+from .scope import render_scope
+from .style import Style
+from .syntax import Syntax
+from .text import Text
+from .theme import Theme
+
+WINDOWS = platform.system() == "Windows"
+
+LOCALS_MAX_LENGTH = 10
+LOCALS_MAX_STRING = 80
+
+
+def install(
+ *,
+ console: Optional[Console] = None,
+ width: Optional[int] = 100,
+ extra_lines: int = 3,
+ theme: Optional[str] = None,
+ word_wrap: bool = False,
+ show_locals: bool = False,
+ indent_guides: bool = True,
+ suppress: Iterable[Union[str, ModuleType]] = (),
+ max_frames: int = 100,
+) -> Callable[[Type[BaseException], BaseException, Optional[TracebackType]], Any]:
+ """Install a rich traceback handler.
+
+ Once installed, any tracebacks will be printed with syntax highlighting and rich formatting.
+
+
+ Args:
+ console (Optional[Console], optional): Console to write exception to. Default uses internal Console instance.
+ width (Optional[int], optional): Width (in characters) of traceback. Defaults to 100.
+ extra_lines (int, optional): Extra lines of code. Defaults to 3.
+ theme (Optional[str], optional): Pygments theme to use in traceback. Defaults to ``None`` which will pick
+ a theme appropriate for the platform.
+ word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
+ show_locals (bool, optional): Enable display of local variables. Defaults to False.
+ indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True.
+ suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
+
+ Returns:
+ Callable: The previous exception handler that was replaced.
+
+ """
+ traceback_console = Console(file=sys.stderr) if console is None else console
+
+ def excepthook(
+ type_: Type[BaseException],
+ value: BaseException,
+ traceback: Optional[TracebackType],
+ ) -> None:
+ traceback_console.print(
+ Traceback.from_exception(
+ type_,
+ value,
+ traceback,
+ width=width,
+ extra_lines=extra_lines,
+ theme=theme,
+ word_wrap=word_wrap,
+ show_locals=show_locals,
+ indent_guides=indent_guides,
+ suppress=suppress,
+ max_frames=max_frames,
+ )
+ )
+
+ def ipy_excepthook_closure(ip: Any) -> None: # pragma: no cover
+ tb_data = {} # store information about showtraceback call
+ default_showtraceback = ip.showtraceback # keep reference of default traceback
+
+ def ipy_show_traceback(*args: Any, **kwargs: Any) -> None:
+ """wrap the default ip.showtraceback to store info for ip._showtraceback"""
+ nonlocal tb_data
+ tb_data = kwargs
+ default_showtraceback(*args, **kwargs)
+
+ def ipy_display_traceback(
+ *args: Any, is_syntax: bool = False, **kwargs: Any
+ ) -> None:
+ """Internally called traceback from ip._showtraceback"""
+ nonlocal tb_data
+ exc_tuple = ip._get_exc_info()
+
+ # do not display trace on syntax error
+ tb: Optional[TracebackType] = None if is_syntax else exc_tuple[2]
+
+ # determine correct tb_offset
+ compiled = tb_data.get("running_compiled_code", False)
+ tb_offset = tb_data.get("tb_offset", 1 if compiled else 0)
+ # remove ipython internal frames from trace with tb_offset
+ for _ in range(tb_offset):
+ if tb is None:
+ break
+ tb = tb.tb_next
+
+ excepthook(exc_tuple[0], exc_tuple[1], tb)
+ tb_data = {} # clear data upon usage
+
+ # replace _showtraceback instead of showtraceback to allow ipython features such as debugging to work
+ # this is also what the ipython docs recommends to modify when subclassing InteractiveShell
+ ip._showtraceback = ipy_display_traceback
+ # add wrapper to capture tb_data
+ ip.showtraceback = ipy_show_traceback
+ ip.showsyntaxerror = lambda *args, **kwargs: ipy_display_traceback(
+ *args, is_syntax=True, **kwargs
+ )
+
+ try: # pragma: no cover
+ # if within ipython, use customized traceback
+ ip = get_ipython() # type: ignore[name-defined]
+ ipy_excepthook_closure(ip)
+ return sys.excepthook
+ except Exception:
+ # otherwise use default system hook
+ old_excepthook = sys.excepthook
+ sys.excepthook = excepthook
+ return old_excepthook
+
+
+@dataclass
+class Frame:
+ filename: str
+ lineno: int
+ name: str
+ line: str = ""
+ locals: Optional[Dict[str, pretty.Node]] = None
+
+
+@dataclass
+class _SyntaxError:
+ offset: int
+ filename: str
+ line: str
+ lineno: int
+ msg: str
+
+
+@dataclass
+class Stack:
+ exc_type: str
+ exc_value: str
+ syntax_error: Optional[_SyntaxError] = None
+ is_cause: bool = False
+ frames: List[Frame] = field(default_factory=list)
+
+
+@dataclass
+class Trace:
+ stacks: List[Stack]
+
+
+class PathHighlighter(RegexHighlighter):
+ highlights = [r"(?P<dim>.*/)(?P<bold>.+)"]
+
+
+class Traceback:
+ """A Console renderable that renders a traceback.
+
+ Args:
+ trace (Trace, optional): A `Trace` object produced from `extract`. Defaults to None, which uses
+ the last exception.
+ width (Optional[int], optional): Number of characters used to traceback. Defaults to 100.
+ extra_lines (int, optional): Additional lines of code to render. Defaults to 3.
+ theme (str, optional): Override pygments theme used in traceback.
+ word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
+ show_locals (bool, optional): Enable display of local variables. Defaults to False.
+ indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True.
+ locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
+ Defaults to 10.
+ locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
+ suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
+ max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
+
+ """
+
+ LEXERS = {
+ "": "text",
+ ".py": "python",
+ ".pxd": "cython",
+ ".pyx": "cython",
+ ".pxi": "pyrex",
+ }
+
+ def __init__(
+ self,
+ trace: Optional[Trace] = None,
+ width: Optional[int] = 100,
+ extra_lines: int = 3,
+ theme: Optional[str] = None,
+ word_wrap: bool = False,
+ show_locals: bool = False,
+ indent_guides: bool = True,
+ locals_max_length: int = LOCALS_MAX_LENGTH,
+ locals_max_string: int = LOCALS_MAX_STRING,
+ suppress: Iterable[Union[str, ModuleType]] = (),
+ max_frames: int = 100,
+ ):
+ if trace is None:
+ exc_type, exc_value, traceback = sys.exc_info()
+ if exc_type is None or exc_value is None or traceback is None:
+ raise ValueError(
+ "Value for 'trace' required if not called in except: block"
+ )
+ trace = self.extract(
+ exc_type, exc_value, traceback, show_locals=show_locals
+ )
+ self.trace = trace
+ self.width = width
+ self.extra_lines = extra_lines
+ self.theme = Syntax.get_theme(theme or "ansi_dark")
+ self.word_wrap = word_wrap
+ self.show_locals = show_locals
+ self.indent_guides = indent_guides
+ self.locals_max_length = locals_max_length
+ self.locals_max_string = locals_max_string
+
+ self.suppress: Sequence[str] = []
+ for suppress_entity in suppress:
+ if not isinstance(suppress_entity, str):
+ assert (
+ suppress_entity.__file__ is not None
+ ), f"{suppress_entity!r} must be a module with '__file__' attribute"
+ path = os.path.dirname(suppress_entity.__file__)
+ else:
+ path = suppress_entity
+ path = os.path.normpath(os.path.abspath(path))
+ self.suppress.append(path)
+ self.max_frames = max(4, max_frames) if max_frames > 0 else 0
+
+ @classmethod
+ def from_exception(
+ cls,
+ exc_type: Type[Any],
+ exc_value: BaseException,
+ traceback: Optional[TracebackType],
+ width: Optional[int] = 100,
+ extra_lines: int = 3,
+ theme: Optional[str] = None,
+ word_wrap: bool = False,
+ show_locals: bool = False,
+ indent_guides: bool = True,
+ locals_max_length: int = LOCALS_MAX_LENGTH,
+ locals_max_string: int = LOCALS_MAX_STRING,
+ suppress: Iterable[Union[str, ModuleType]] = (),
+ max_frames: int = 100,
+ ) -> "Traceback":
+ """Create a traceback from exception info
+
+ Args:
+ exc_type (Type[BaseException]): Exception type.
+ exc_value (BaseException): Exception value.
+ traceback (TracebackType): Python Traceback object.
+ width (Optional[int], optional): Number of characters used to traceback. Defaults to 100.
+ extra_lines (int, optional): Additional lines of code to render. Defaults to 3.
+ theme (str, optional): Override pygments theme used in traceback.
+ word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
+ show_locals (bool, optional): Enable display of local variables. Defaults to False.
+ indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True.
+ locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
+ Defaults to 10.
+ locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
+ suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
+ max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
+
+ Returns:
+ Traceback: A Traceback instance that may be printed.
+ """
+ rich_traceback = cls.extract(
+ exc_type, exc_value, traceback, show_locals=show_locals
+ )
+ return cls(
+ rich_traceback,
+ width=width,
+ extra_lines=extra_lines,
+ theme=theme,
+ word_wrap=word_wrap,
+ show_locals=show_locals,
+ indent_guides=indent_guides,
+ locals_max_length=locals_max_length,
+ locals_max_string=locals_max_string,
+ suppress=suppress,
+ max_frames=max_frames,
+ )
+
+ @classmethod
+ def extract(
+ cls,
+ exc_type: Type[BaseException],
+ exc_value: BaseException,
+ traceback: Optional[TracebackType],
+ show_locals: bool = False,
+ locals_max_length: int = LOCALS_MAX_LENGTH,
+ locals_max_string: int = LOCALS_MAX_STRING,
+ ) -> Trace:
+ """Extract traceback information.
+
+ Args:
+ exc_type (Type[BaseException]): Exception type.
+ exc_value (BaseException): Exception value.
+ traceback (TracebackType): Python Traceback object.
+ show_locals (bool, optional): Enable display of local variables. Defaults to False.
+ locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
+ Defaults to 10.
+ locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
+
+ Returns:
+ Trace: A Trace instance which you can use to construct a `Traceback`.
+ """
+
+ stacks: List[Stack] = []
+ is_cause = False
+
+ from pip._vendor.rich import _IMPORT_CWD
+
+ def safe_str(_object: Any) -> str:
+ """Don't allow exceptions from __str__ to propagate."""
+ try:
+ return str(_object)
+ except Exception:
+ return "<exception str() failed>"
+
+ while True:
+ stack = Stack(
+ exc_type=safe_str(exc_type.__name__),
+ exc_value=safe_str(exc_value),
+ is_cause=is_cause,
+ )
+
+ if isinstance(exc_value, SyntaxError):
+ stack.syntax_error = _SyntaxError(
+ offset=exc_value.offset or 0,
+ filename=exc_value.filename or "?",
+ lineno=exc_value.lineno or 0,
+ line=exc_value.text or "",
+ msg=exc_value.msg,
+ )
+
+ stacks.append(stack)
+ append = stack.frames.append
+
+ for frame_summary, line_no in walk_tb(traceback):
+ filename = frame_summary.f_code.co_filename
+ if filename and not filename.startswith("<"):
+ if not os.path.isabs(filename):
+ filename = os.path.join(_IMPORT_CWD, filename)
+ if frame_summary.f_locals.get("_rich_traceback_omit", False):
+ continue
+ frame = Frame(
+ filename=filename or "?",
+ lineno=line_no,
+ name=frame_summary.f_code.co_name,
+ locals={
+ key: pretty.traverse(
+ value,
+ max_length=locals_max_length,
+ max_string=locals_max_string,
+ )
+ for key, value in frame_summary.f_locals.items()
+ }
+ if show_locals
+ else None,
+ )
+ append(frame)
+ if frame_summary.f_locals.get("_rich_traceback_guard", False):
+ del stack.frames[:]
+
+ cause = getattr(exc_value, "__cause__", None)
+ if cause:
+ exc_type = cause.__class__
+ exc_value = cause
+ # __traceback__ can be None, e.g. for exceptions raised by the
+ # 'multiprocessing' module
+ traceback = cause.__traceback__
+ is_cause = True
+ continue
+
+ cause = exc_value.__context__
+ if cause and not getattr(exc_value, "__suppress_context__", False):
+ exc_type = cause.__class__
+ exc_value = cause
+ traceback = cause.__traceback__
+ is_cause = False
+ continue
+ # No cover, code is reached but coverage doesn't recognize it.
+ break # pragma: no cover
+
+ trace = Trace(stacks=stacks)
+ return trace
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ theme = self.theme
+ background_style = theme.get_background_style()
+ token_style = theme.get_style_for_token
+
+ traceback_theme = Theme(
+ {
+ "pretty": token_style(TextToken),
+ "pygments.text": token_style(Token),
+ "pygments.string": token_style(String),
+ "pygments.function": token_style(Name.Function),
+ "pygments.number": token_style(Number),
+ "repr.indent": token_style(Comment) + Style(dim=True),
+ "repr.str": token_style(String),
+ "repr.brace": token_style(TextToken) + Style(bold=True),
+ "repr.number": token_style(Number),
+ "repr.bool_true": token_style(Keyword.Constant),
+ "repr.bool_false": token_style(Keyword.Constant),
+ "repr.none": token_style(Keyword.Constant),
+ "scope.border": token_style(String.Delimiter),
+ "scope.equals": token_style(Operator),
+ "scope.key": token_style(Name),
+ "scope.key.special": token_style(Name.Constant) + Style(dim=True),
+ },
+ inherit=False,
+ )
+
+ highlighter = ReprHighlighter()
+ for last, stack in loop_last(reversed(self.trace.stacks)):
+ if stack.frames:
+ stack_renderable: ConsoleRenderable = Panel(
+ self._render_stack(stack),
+ title="[traceback.title]Traceback [dim](most recent call last)",
+ style=background_style,
+ border_style="traceback.border",
+ expand=True,
+ padding=(0, 1),
+ )
+ stack_renderable = Constrain(stack_renderable, self.width)
+ with console.use_theme(traceback_theme):
+ yield stack_renderable
+ if stack.syntax_error is not None:
+ with console.use_theme(traceback_theme):
+ yield Constrain(
+ Panel(
+ self._render_syntax_error(stack.syntax_error),
+ style=background_style,
+ border_style="traceback.border.syntax_error",
+ expand=True,
+ padding=(0, 1),
+ width=self.width,
+ ),
+ self.width,
+ )
+ yield Text.assemble(
+ (f"{stack.exc_type}: ", "traceback.exc_type"),
+ highlighter(stack.syntax_error.msg),
+ )
+ elif stack.exc_value:
+ yield Text.assemble(
+ (f"{stack.exc_type}: ", "traceback.exc_type"),
+ highlighter(stack.exc_value),
+ )
+ else:
+ yield Text.assemble((f"{stack.exc_type}", "traceback.exc_type"))
+
+ if not last:
+ if stack.is_cause:
+ yield Text.from_markup(
+ "\n[i]The above exception was the direct cause of the following exception:\n",
+ )
+ else:
+ yield Text.from_markup(
+ "\n[i]During handling of the above exception, another exception occurred:\n",
+ )
+
+ @group()
+ def _render_syntax_error(self, syntax_error: _SyntaxError) -> RenderResult:
+ highlighter = ReprHighlighter()
+ path_highlighter = PathHighlighter()
+ if syntax_error.filename != "<stdin>":
+ text = Text.assemble(
+ (f" {syntax_error.filename}", "pygments.string"),
+ (":", "pygments.text"),
+ (str(syntax_error.lineno), "pygments.number"),
+ style="pygments.text",
+ )
+ yield path_highlighter(text)
+ syntax_error_text = highlighter(syntax_error.line.rstrip())
+ syntax_error_text.no_wrap = True
+ offset = min(syntax_error.offset - 1, len(syntax_error_text))
+ syntax_error_text.stylize("bold underline", offset, offset)
+ syntax_error_text += Text.from_markup(
+ "\n" + " " * offset + "[traceback.offset]▲[/]",
+ style="pygments.text",
+ )
+ yield syntax_error_text
+
+ @classmethod
+ def _guess_lexer(cls, filename: str, code: str) -> str:
+ ext = os.path.splitext(filename)[-1]
+ if not ext:
+ # No extension, look at first line to see if it is a hashbang
+ # Note, this is an educated guess and not a guarantee
+ # If it fails, the only downside is that the code is highlighted strangely
+ new_line_index = code.index("\n")
+ first_line = code[:new_line_index] if new_line_index != -1 else code
+ if first_line.startswith("#!") and "python" in first_line.lower():
+ return "python"
+ try:
+ return cls.LEXERS.get(ext) or guess_lexer_for_filename(filename, code).name
+ except ClassNotFound:
+ return "text"
+
+ @group()
+ def _render_stack(self, stack: Stack) -> RenderResult:
+ path_highlighter = PathHighlighter()
+ theme = self.theme
+ code_cache: Dict[str, str] = {}
+
+ def read_code(filename: str) -> str:
+ """Read files, and cache results on filename.
+
+ Args:
+ filename (str): Filename to read
+
+ Returns:
+ str: Contents of file
+ """
+ code = code_cache.get(filename)
+ if code is None:
+ with open(
+ filename, "rt", encoding="utf-8", errors="replace"
+ ) as code_file:
+ code = code_file.read()
+ code_cache[filename] = code
+ return code
+
+ def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]:
+ if frame.locals:
+ yield render_scope(
+ frame.locals,
+ title="locals",
+ indent_guides=self.indent_guides,
+ max_length=self.locals_max_length,
+ max_string=self.locals_max_string,
+ )
+
+ exclude_frames: Optional[range] = None
+ if self.max_frames != 0:
+ exclude_frames = range(
+ self.max_frames // 2,
+ len(stack.frames) - self.max_frames // 2,
+ )
+
+ excluded = False
+ for frame_index, frame in enumerate(stack.frames):
+
+ if exclude_frames and frame_index in exclude_frames:
+ excluded = True
+ continue
+
+ if excluded:
+ assert exclude_frames is not None
+ yield Text(
+ f"\n... {len(exclude_frames)} frames hidden ...",
+ justify="center",
+ style="traceback.error",
+ )
+ excluded = False
+
+ first = frame_index == 0
+ frame_filename = frame.filename
+ suppressed = any(frame_filename.startswith(path) for path in self.suppress)
+
+ text = Text.assemble(
+ path_highlighter(Text(frame.filename, style="pygments.string")),
+ (":", "pygments.text"),
+ (str(frame.lineno), "pygments.number"),
+ " in ",
+ (frame.name, "pygments.function"),
+ style="pygments.text",
+ )
+ if not frame.filename.startswith("<") and not first:
+ yield ""
+ yield text
+ if frame.filename.startswith("<"):
+ yield from render_locals(frame)
+ continue
+ if not suppressed:
+ try:
+ code = read_code(frame.filename)
+ lexer_name = self._guess_lexer(frame.filename, code)
+ syntax = Syntax(
+ code,
+ lexer_name,
+ theme=theme,
+ line_numbers=True,
+ line_range=(
+ frame.lineno - self.extra_lines,
+ frame.lineno + self.extra_lines,
+ ),
+ highlight_lines={frame.lineno},
+ word_wrap=self.word_wrap,
+ code_width=88,
+ indent_guides=self.indent_guides,
+ dedent=False,
+ )
+ yield ""
+ except Exception as error:
+ yield Text.assemble(
+ (f"\n{error}", "traceback.error"),
+ )
+ else:
+ yield (
+ Columns(
+ [
+ syntax,
+ *render_locals(frame),
+ ],
+ padding=1,
+ )
+ if frame.locals
+ else syntax
+ )
+
+
+if __name__ == "__main__": # pragma: no cover
+
+ from .console import Console
+
+ console = Console()
+ import sys
+
+ def bar(a: Any) -> None: # 这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑
+ one = 1
+ print(one / a)
+
+ def foo(a: Any) -> None:
+ _rich_traceback_guard = True
+ zed = {
+ "characters": {
+ "Paul Atreides",
+ "Vladimir Harkonnen",
+ "Thufir Hawat",
+ "Duncan Idaho",
+ },
+ "atomic_types": (None, False, True),
+ }
+ bar(a)
+
+ def error() -> None:
+
+ try:
+ try:
+ foo(0)
+ except:
+ slfkjsldkfj # type: ignore[name-defined]
+ except:
+ console.print_exception(show_locals=True)
+
+ error()
diff --git a/src/pip/_vendor/rich/tree.py b/src/pip/_vendor/rich/tree.py
new file mode 100644
index 000000000..afe8da1a4
--- /dev/null
+++ b/src/pip/_vendor/rich/tree.py
@@ -0,0 +1,251 @@
+from typing import Iterator, List, Optional, Tuple
+
+from ._loop import loop_first, loop_last
+from .console import Console, ConsoleOptions, RenderableType, RenderResult
+from .jupyter import JupyterMixin
+from .measure import Measurement
+from .segment import Segment
+from .style import Style, StyleStack, StyleType
+from .styled import Styled
+
+
+class Tree(JupyterMixin):
+ """A renderable for a tree structure.
+
+ Args:
+ label (RenderableType): The renderable or str for the tree label.
+ style (StyleType, optional): Style of this tree. Defaults to "tree".
+ guide_style (StyleType, optional): Style of the guide lines. Defaults to "tree.line".
+ expanded (bool, optional): Also display children. Defaults to True.
+ highlight (bool, optional): Highlight renderable (if str). Defaults to False.
+ """
+
+ def __init__(
+ self,
+ label: RenderableType,
+ *,
+ style: StyleType = "tree",
+ guide_style: StyleType = "tree.line",
+ expanded: bool = True,
+ highlight: bool = False,
+ hide_root: bool = False,
+ ) -> None:
+ self.label = label
+ self.style = style
+ self.guide_style = guide_style
+ self.children: List[Tree] = []
+ self.expanded = expanded
+ self.highlight = highlight
+ self.hide_root = hide_root
+
+ def add(
+ self,
+ label: RenderableType,
+ *,
+ style: Optional[StyleType] = None,
+ guide_style: Optional[StyleType] = None,
+ expanded: bool = True,
+ highlight: Optional[bool] = False,
+ ) -> "Tree":
+ """Add a child tree.
+
+ Args:
+ label (RenderableType): The renderable or str for the tree label.
+ style (StyleType, optional): Style of this tree. Defaults to "tree".
+ guide_style (StyleType, optional): Style of the guide lines. Defaults to "tree.line".
+ expanded (bool, optional): Also display children. Defaults to True.
+ highlight (Optional[bool], optional): Highlight renderable (if str). Defaults to False.
+
+ Returns:
+ Tree: A new child Tree, which may be further modified.
+ """
+ node = Tree(
+ label,
+ style=self.style if style is None else style,
+ guide_style=self.guide_style if guide_style is None else guide_style,
+ expanded=expanded,
+ highlight=self.highlight if highlight is None else highlight,
+ )
+ self.children.append(node)
+ return node
+
+ def __rich_console__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "RenderResult":
+
+ stack: List[Iterator[Tuple[bool, Tree]]] = []
+ pop = stack.pop
+ push = stack.append
+ new_line = Segment.line()
+
+ get_style = console.get_style
+ null_style = Style.null()
+ guide_style = get_style(self.guide_style, default="") or null_style
+ SPACE, CONTINUE, FORK, END = range(4)
+
+ ASCII_GUIDES = (" ", "| ", "+-- ", "`-- ")
+ TREE_GUIDES = [
+ (" ", "│ ", "├── ", "└── "),
+ (" ", "┃ ", "┣━━ ", "┗━━ "),
+ (" ", "║ ", "╠══ ", "╚══ "),
+ ]
+ _Segment = Segment
+
+ def make_guide(index: int, style: Style) -> Segment:
+ """Make a Segment for a level of the guide lines."""
+ if options.ascii_only:
+ line = ASCII_GUIDES[index]
+ else:
+ guide = 1 if style.bold else (2 if style.underline2 else 0)
+ line = TREE_GUIDES[0 if options.legacy_windows else guide][index]
+ return _Segment(line, style)
+
+ levels: List[Segment] = [make_guide(CONTINUE, guide_style)]
+ push(iter(loop_last([self])))
+
+ guide_style_stack = StyleStack(get_style(self.guide_style))
+ style_stack = StyleStack(get_style(self.style))
+ remove_guide_styles = Style(bold=False, underline2=False)
+
+ depth = 0
+
+ while stack:
+ stack_node = pop()
+ try:
+ last, node = next(stack_node)
+ except StopIteration:
+ levels.pop()
+ if levels:
+ guide_style = levels[-1].style or null_style
+ levels[-1] = make_guide(FORK, guide_style)
+ guide_style_stack.pop()
+ style_stack.pop()
+ continue
+ push(stack_node)
+ if last:
+ levels[-1] = make_guide(END, levels[-1].style or null_style)
+
+ guide_style = guide_style_stack.current + get_style(node.guide_style)
+ style = style_stack.current + get_style(node.style)
+ prefix = levels[(2 if self.hide_root else 1) :]
+ renderable_lines = console.render_lines(
+ Styled(node.label, style),
+ options.update(
+ width=options.max_width
+ - sum(level.cell_length for level in prefix),
+ highlight=self.highlight,
+ height=None,
+ ),
+ pad=options.justify is not None,
+ )
+
+ if not (depth == 0 and self.hide_root):
+ for first, line in loop_first(renderable_lines):
+ if prefix:
+ yield from _Segment.apply_style(
+ prefix,
+ style.background_style,
+ post_style=remove_guide_styles,
+ )
+ yield from line
+ yield new_line
+ if first and prefix:
+ prefix[-1] = make_guide(
+ SPACE if last else CONTINUE, prefix[-1].style or null_style
+ )
+
+ if node.expanded and node.children:
+ levels[-1] = make_guide(
+ SPACE if last else CONTINUE, levels[-1].style or null_style
+ )
+ levels.append(
+ make_guide(END if len(node.children) == 1 else FORK, guide_style)
+ )
+ style_stack.push(get_style(node.style))
+ guide_style_stack.push(get_style(node.guide_style))
+ push(iter(loop_last(node.children)))
+ depth += 1
+
+ def __rich_measure__(
+ self, console: "Console", options: "ConsoleOptions"
+ ) -> "Measurement":
+ stack: List[Iterator[Tree]] = [iter([self])]
+ pop = stack.pop
+ push = stack.append
+ minimum = 0
+ maximum = 0
+ measure = Measurement.get
+ level = 0
+ while stack:
+ iter_tree = pop()
+ try:
+ tree = next(iter_tree)
+ except StopIteration:
+ level -= 1
+ continue
+ push(iter_tree)
+ min_measure, max_measure = measure(console, options, tree.label)
+ indent = level * 4
+ minimum = max(min_measure + indent, minimum)
+ maximum = max(max_measure + indent, maximum)
+ if tree.expanded and tree.children:
+ push(iter(tree.children))
+ level += 1
+ return Measurement(minimum, maximum)
+
+
+if __name__ == "__main__": # pragma: no cover
+
+ from pip._vendor.rich.console import Group
+ from pip._vendor.rich.markdown import Markdown
+ from pip._vendor.rich.panel import Panel
+ from pip._vendor.rich.syntax import Syntax
+ from pip._vendor.rich.table import Table
+
+ table = Table(row_styles=["", "dim"])
+
+ table.add_column("Released", style="cyan", no_wrap=True)
+ table.add_column("Title", style="magenta")
+ table.add_column("Box Office", justify="right", style="green")
+
+ table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690")
+ table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
+ table.add_row("Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889")
+ table.add_row("Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889")
+
+ code = """\
+class Segment(NamedTuple):
+ text: str = ""
+ style: Optional[Style] = None
+ is_control: bool = False
+"""
+ syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
+
+ markdown = Markdown(
+ """\
+### example.md
+> Hello, World!
+>
+> Markdown _all_ the things
+"""
+ )
+
+ root = Tree("🌲 [b green]Rich Tree", highlight=True, hide_root=True)
+
+ node = root.add(":file_folder: Renderables", guide_style="red")
+ simple_node = node.add(":file_folder: [bold yellow]Atomic", guide_style="uu green")
+ simple_node.add(Group("📄 Syntax", syntax))
+ simple_node.add(Group("📄 Markdown", Panel(markdown, border_style="green")))
+
+ containers_node = node.add(
+ ":file_folder: [bold magenta]Containers", guide_style="bold magenta"
+ )
+ containers_node.expanded = True
+ panel = Panel.fit("Just a panel", border_style="red")
+ containers_node.add(Group("📄 Panels", panel))
+
+ containers_node.add(Group("📄 [b magenta]Table", table))
+
+ console = Console()
+
+ console.print(root)
diff --git a/src/pip/_vendor/tenacity/__init__.py b/src/pip/_vendor/tenacity/__init__.py
index 086ad46e1..ab3be3bf6 100644
--- a/src/pip/_vendor/tenacity/__init__.py
+++ b/src/pip/_vendor/tenacity/__init__.py
@@ -33,6 +33,7 @@ from .retry import retry_always # noqa
from .retry import retry_any # noqa
from .retry import retry_if_exception # noqa
from .retry import retry_if_exception_type # noqa
+from .retry import retry_if_exception_cause_type # noqa
from .retry import retry_if_not_exception_type # noqa
from .retry import retry_if_not_result # noqa
from .retry import retry_if_result # noqa
@@ -63,6 +64,7 @@ from .wait import wait_none # noqa
from .wait import wait_random # noqa
from .wait import wait_random_exponential # noqa
from .wait import wait_random_exponential as wait_full_jitter # noqa
+from .wait import wait_exponential_jitter # noqa
# Import all built-in before strategies for easier usage.
from .before import before_log # noqa
diff --git a/src/pip/_vendor/tenacity/retry.py b/src/pip/_vendor/tenacity/retry.py
index 1d727e9b3..9ebeb62d5 100644
--- a/src/pip/_vendor/tenacity/retry.py
+++ b/src/pip/_vendor/tenacity/retry.py
@@ -117,6 +117,33 @@ class retry_unless_exception_type(retry_if_exception):
return self.predicate(retry_state.outcome.exception())
+class retry_if_exception_cause_type(retry_base):
+ """Retries if any of the causes of the raised exception is of one or more types.
+
+ The check on the type of the cause of the exception is done recursively (until finding
+ an exception in the chain that has no `__cause__`)
+ """
+
+ def __init__(
+ self,
+ exception_types: typing.Union[
+ typing.Type[BaseException],
+ typing.Tuple[typing.Type[BaseException], ...],
+ ] = Exception,
+ ) -> None:
+ self.exception_cause_types = exception_types
+
+ def __call__(self, retry_state: "RetryCallState") -> bool:
+ if retry_state.outcome.failed:
+ exc = retry_state.outcome.exception()
+ while exc is not None:
+ if isinstance(exc.__cause__, self.exception_cause_types):
+ return True
+ exc = exc.__cause__
+
+ return False
+
+
class retry_if_result(retry_base):
"""Retries if the result verifies a predicate."""
diff --git a/src/pip/_vendor/tenacity/wait.py b/src/pip/_vendor/tenacity/wait.py
index 6ed97a7bc..8fdfc8f9d 100644
--- a/src/pip/_vendor/tenacity/wait.py
+++ b/src/pip/_vendor/tenacity/wait.py
@@ -17,12 +17,19 @@
import abc
import random
import typing
+from datetime import timedelta
from pip._vendor.tenacity import _utils
if typing.TYPE_CHECKING:
from pip._vendor.tenacity import RetryCallState
+wait_unit_type = typing.Union[int, float, timedelta]
+
+
+def to_seconds(wait_unit: wait_unit_type) -> float:
+ return float(wait_unit.total_seconds() if isinstance(wait_unit, timedelta) else wait_unit)
+
class wait_base(abc.ABC):
"""Abstract base class for wait strategies."""
@@ -44,8 +51,8 @@ class wait_base(abc.ABC):
class wait_fixed(wait_base):
"""Wait strategy that waits a fixed amount of time between each retry."""
- def __init__(self, wait: float) -> None:
- self.wait_fixed = wait
+ def __init__(self, wait: wait_unit_type) -> None:
+ self.wait_fixed = to_seconds(wait)
def __call__(self, retry_state: "RetryCallState") -> float:
return self.wait_fixed
@@ -61,9 +68,9 @@ class wait_none(wait_fixed):
class wait_random(wait_base):
"""Wait strategy that waits a random amount of time between min/max."""
- def __init__(self, min: typing.Union[int, float] = 0, max: typing.Union[int, float] = 1) -> None: # noqa
- self.wait_random_min = min
- self.wait_random_max = max
+ def __init__(self, min: wait_unit_type = 0, max: wait_unit_type = 1) -> None: # noqa
+ self.wait_random_min = to_seconds(min)
+ self.wait_random_max = to_seconds(max)
def __call__(self, retry_state: "RetryCallState") -> float:
return self.wait_random_min + (random.random() * (self.wait_random_max - self.wait_random_min))
@@ -113,13 +120,13 @@ class wait_incrementing(wait_base):
def __init__(
self,
- start: typing.Union[int, float] = 0,
- increment: typing.Union[int, float] = 100,
- max: typing.Union[int, float] = _utils.MAX_WAIT, # noqa
+ start: wait_unit_type = 0,
+ increment: wait_unit_type = 100,
+ max: wait_unit_type = _utils.MAX_WAIT, # noqa
) -> None:
- self.start = start
- self.increment = increment
- self.max = max
+ self.start = to_seconds(start)
+ self.increment = to_seconds(increment)
+ self.max = to_seconds(max)
def __call__(self, retry_state: "RetryCallState") -> float:
result = self.start + (self.increment * (retry_state.attempt_number - 1))
@@ -142,13 +149,13 @@ class wait_exponential(wait_base):
def __init__(
self,
multiplier: typing.Union[int, float] = 1,
- max: typing.Union[int, float] = _utils.MAX_WAIT, # noqa
+ max: wait_unit_type = _utils.MAX_WAIT, # noqa
exp_base: typing.Union[int, float] = 2,
- min: typing.Union[int, float] = 0, # noqa
+ min: wait_unit_type = 0, # noqa
) -> None:
self.multiplier = multiplier
- self.min = min
- self.max = max
+ self.min = to_seconds(min)
+ self.max = to_seconds(max)
self.exp_base = exp_base
def __call__(self, retry_state: "RetryCallState") -> float:
@@ -189,3 +196,37 @@ class wait_random_exponential(wait_exponential):
def __call__(self, retry_state: "RetryCallState") -> float:
high = super().__call__(retry_state=retry_state)
return random.uniform(0, high)
+
+
+class wait_exponential_jitter(wait_base):
+ """Wait strategy that applies exponential backoff and jitter.
+
+ It allows for a customized initial wait, maximum wait and jitter.
+
+ This implements the strategy described here:
+ https://cloud.google.com/storage/docs/retry-strategy
+
+ The wait time is min(initial * (2**n + random.uniform(0, jitter)), maximum)
+ where n is the retry count.
+ """
+
+ def __init__(
+ self,
+ initial: float = 1,
+ max: float = _utils.MAX_WAIT, # noqa
+ exp_base: float = 2,
+ jitter: float = 1,
+ ) -> None:
+ self.initial = initial
+ self.max = max
+ self.exp_base = exp_base
+ self.jitter = jitter
+
+ def __call__(self, retry_state: "RetryCallState") -> float:
+ jitter = random.uniform(0, self.jitter)
+ try:
+ exp = self.exp_base ** (retry_state.attempt_number - 1)
+ result = self.initial * exp + jitter
+ except OverflowError:
+ result = self.max
+ return max(0, min(result, self.max))
diff --git a/src/pip/_vendor/tomli/__init__.py b/src/pip/_vendor/tomli/__init__.py
index 1cd8e0727..4c6ec97ec 100644
--- a/src/pip/_vendor/tomli/__init__.py
+++ b/src/pip/_vendor/tomli/__init__.py
@@ -1,6 +1,11 @@
-"""A lil' TOML parser."""
+# SPDX-License-Identifier: MIT
+# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
+# Licensed to PSF under a Contributor Agreement.
__all__ = ("loads", "load", "TOMLDecodeError")
-__version__ = "1.0.3" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT
+__version__ = "2.0.1" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT
-from pip._vendor.tomli._parser import TOMLDecodeError, load, loads
+from ._parser import TOMLDecodeError, load, loads
+
+# Pretend this exception was created here.
+TOMLDecodeError.__module__ = __name__
diff --git a/src/pip/_vendor/tomli/_parser.py b/src/pip/_vendor/tomli/_parser.py
index 730a74684..f1bb0aa19 100644
--- a/src/pip/_vendor/tomli/_parser.py
+++ b/src/pip/_vendor/tomli/_parser.py
@@ -1,42 +1,33 @@
+# SPDX-License-Identifier: MIT
+# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
+# Licensed to PSF under a Contributor Agreement.
+
+from __future__ import annotations
+
+from collections.abc import Iterable
import string
from types import MappingProxyType
-from typing import (
- TYPE_CHECKING,
- Any,
- Callable,
- Dict,
- FrozenSet,
- Iterable,
- Optional,
- TextIO,
- Tuple,
-)
+from typing import Any, BinaryIO, NamedTuple
-from pip._vendor.tomli._re import (
- RE_BIN,
+from ._re import (
RE_DATETIME,
- RE_HEX,
RE_LOCALTIME,
RE_NUMBER,
- RE_OCT,
match_to_datetime,
match_to_localtime,
match_to_number,
)
-
-if TYPE_CHECKING:
- from re import Pattern
-
+from ._types import Key, ParseFloat, Pos
ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
# Neither of these sets include quotation mark or backslash. They are
# currently handled as separate cases in the parser functions.
ILLEGAL_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t")
-ILLEGAL_MULTILINE_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t\n\r")
+ILLEGAL_MULTILINE_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t\n")
ILLEGAL_LITERAL_STR_CHARS = ILLEGAL_BASIC_STR_CHARS
-ILLEGAL_MULTILINE_LITERAL_STR_CHARS = ASCII_CTRL - frozenset("\t\n")
+ILLEGAL_MULTILINE_LITERAL_STR_CHARS = ILLEGAL_MULTILINE_BASIC_STR_CHARS
ILLEGAL_COMMENT_CHARS = ILLEGAL_BASIC_STR_CHARS
@@ -44,6 +35,7 @@ TOML_WS = frozenset(" \t")
TOML_WS_AND_NEWLINE = TOML_WS | frozenset("\n")
BARE_KEY_CHARS = frozenset(string.ascii_letters + string.digits + "-_")
KEY_INITIAL_CHARS = BARE_KEY_CHARS | frozenset("\"'")
+HEXDIGIT_CHARS = frozenset(string.hexdigits)
BASIC_STR_ESCAPE_REPLACEMENTS = MappingProxyType(
{
@@ -57,30 +49,33 @@ BASIC_STR_ESCAPE_REPLACEMENTS = MappingProxyType(
}
)
-# Type annotations
-ParseFloat = Callable[[str], Any]
-Key = Tuple[str, ...]
-Pos = int
-
class TOMLDecodeError(ValueError):
"""An error raised if a document is not valid TOML."""
-def load(fp: TextIO, *, parse_float: ParseFloat = float) -> Dict[str, Any]:
- """Parse TOML from a file object."""
- s = fp.read()
+def load(__fp: BinaryIO, *, parse_float: ParseFloat = float) -> dict[str, Any]:
+ """Parse TOML from a binary file object."""
+ b = __fp.read()
+ try:
+ s = b.decode()
+ except AttributeError:
+ raise TypeError(
+ "File must be opened in binary mode, e.g. use `open('foo.toml', 'rb')`"
+ ) from None
return loads(s, parse_float=parse_float)
-def loads(s: str, *, parse_float: ParseFloat = float) -> Dict[str, Any]: # noqa: C901
+def loads(__s: str, *, parse_float: ParseFloat = float) -> dict[str, Any]: # noqa: C901
"""Parse TOML from a string."""
# The spec allows converting "\r\n" to "\n", even in string
# literals. Let's do so to simplify parsing.
- src = s.replace("\r\n", "\n")
+ src = __s.replace("\r\n", "\n")
pos = 0
- state = State()
+ out = Output(NestedDict(), Flags())
+ header: Key = ()
+ parse_float = make_safe_parse_float(parse_float)
# Parse one statement at a time
# (typically means one line in TOML source)
@@ -104,17 +99,18 @@ def loads(s: str, *, parse_float: ParseFloat = float) -> Dict[str, Any]: # noqa
pos += 1
continue
if char in KEY_INITIAL_CHARS:
- pos = key_value_rule(src, pos, state, parse_float)
+ pos = key_value_rule(src, pos, out, header, parse_float)
pos = skip_chars(src, pos, TOML_WS)
elif char == "[":
try:
- second_char: Optional[str] = src[pos + 1]
+ second_char: str | None = src[pos + 1]
except IndexError:
second_char = None
+ out.flags.finalize_pending()
if second_char == "[":
- pos = create_list_rule(src, pos, state)
+ pos, header = create_list_rule(src, pos, out)
else:
- pos = create_dict_rule(src, pos, state)
+ pos, header = create_dict_rule(src, pos, out)
pos = skip_chars(src, pos, TOML_WS)
elif char != "#":
raise suffixed_err(src, pos, "Invalid statement")
@@ -133,17 +129,7 @@ def loads(s: str, *, parse_float: ParseFloat = float) -> Dict[str, Any]: # noqa
)
pos += 1
- return state.out.dict
-
-
-class State:
- def __init__(self) -> None:
- # Mutable, read-only
- self.out = NestedDict()
- self.flags = Flags()
-
- # Immutable, read and write
- self.header_namespace: Key = ()
+ return out.data.dict
class Flags:
@@ -156,7 +142,16 @@ class Flags:
EXPLICIT_NEST = 1
def __init__(self) -> None:
- self._flags: Dict[str, dict] = {}
+ self._flags: dict[str, dict] = {}
+ self._pending_flags: set[tuple[Key, int]] = set()
+
+ def add_pending(self, key: Key, flag: int) -> None:
+ self._pending_flags.add((key, flag))
+
+ def finalize_pending(self) -> None:
+ for key, flag in self._pending_flags:
+ self.set(key, flag, recursive=False)
+ self._pending_flags.clear()
def unset_all(self, key: Key) -> None:
cont = self._flags
@@ -166,19 +161,6 @@ class Flags:
cont = cont[k]["nested"]
cont.pop(key[-1], None)
- def set_for_relative_key(self, head_key: Key, rel_key: Key, flag: int) -> None:
- cont = self._flags
- for k in head_key:
- if k not in cont:
- cont[k] = {"flags": set(), "recursive_flags": set(), "nested": {}}
- cont = cont[k]["nested"]
- for k in rel_key:
- if k in cont:
- cont[k]["flags"].add(flag)
- else:
- cont[k] = {"flags": {flag}, "recursive_flags": set(), "nested": {}}
- cont = cont[k]["nested"]
-
def set(self, key: Key, flag: int, *, recursive: bool) -> None: # noqa: A003
cont = self._flags
key_parent, key_stem = key[:-1], key[-1]
@@ -211,7 +193,7 @@ class Flags:
class NestedDict:
def __init__(self) -> None:
# The parsed content of the TOML document
- self.dict: Dict[str, Any] = {}
+ self.dict: dict[str, Any] = {}
def get_or_create_nest(
self,
@@ -242,6 +224,11 @@ class NestedDict:
cont[last_key] = [{}]
+class Output(NamedTuple):
+ data: NestedDict
+ flags: Flags
+
+
def skip_chars(src: str, pos: Pos, chars: Iterable[str]) -> Pos:
try:
while src[pos] in chars:
@@ -256,7 +243,7 @@ def skip_until(
pos: Pos,
expect: str,
*,
- error_on: FrozenSet[str],
+ error_on: frozenset[str],
error_on_eof: bool,
) -> Pos:
try:
@@ -264,19 +251,18 @@ def skip_until(
except ValueError:
new_pos = len(src)
if error_on_eof:
- raise suffixed_err(src, new_pos, f'Expected "{expect!r}"')
+ raise suffixed_err(src, new_pos, f"Expected {expect!r}") from None
- bad_chars = error_on.intersection(src[pos:new_pos])
- if bad_chars:
- bad_char = next(iter(bad_chars))
- bad_pos = src.index(bad_char, pos)
- raise suffixed_err(src, bad_pos, f'Found invalid character "{bad_char!r}"')
+ if not error_on.isdisjoint(src[pos:new_pos]):
+ while src[pos] not in error_on:
+ pos += 1
+ raise suffixed_err(src, pos, f"Found invalid character {src[pos]!r}")
return new_pos
def skip_comment(src: str, pos: Pos) -> Pos:
try:
- char: Optional[str] = src[pos]
+ char: str | None = src[pos]
except IndexError:
char = None
if char == "#":
@@ -295,115 +281,116 @@ def skip_comments_and_array_ws(src: str, pos: Pos) -> Pos:
return pos
-def create_dict_rule(src: str, pos: Pos, state: State) -> Pos:
+def create_dict_rule(src: str, pos: Pos, out: Output) -> tuple[Pos, Key]:
pos += 1 # Skip "["
pos = skip_chars(src, pos, TOML_WS)
pos, key = parse_key(src, pos)
- if state.flags.is_(key, Flags.EXPLICIT_NEST) or state.flags.is_(key, Flags.FROZEN):
- raise suffixed_err(src, pos, f"Can not declare {key} twice")
- state.flags.set(key, Flags.EXPLICIT_NEST, recursive=False)
+ if out.flags.is_(key, Flags.EXPLICIT_NEST) or out.flags.is_(key, Flags.FROZEN):
+ raise suffixed_err(src, pos, f"Cannot declare {key} twice")
+ out.flags.set(key, Flags.EXPLICIT_NEST, recursive=False)
try:
- state.out.get_or_create_nest(key)
+ out.data.get_or_create_nest(key)
except KeyError:
- raise suffixed_err(src, pos, "Can not overwrite a value")
- state.header_namespace = key
+ raise suffixed_err(src, pos, "Cannot overwrite a value") from None
- if src[pos : pos + 1] != "]":
- raise suffixed_err(src, pos, 'Expected "]" at the end of a table declaration')
- return pos + 1
+ if not src.startswith("]", pos):
+ raise suffixed_err(src, pos, "Expected ']' at the end of a table declaration")
+ return pos + 1, key
-def create_list_rule(src: str, pos: Pos, state: State) -> Pos:
+def create_list_rule(src: str, pos: Pos, out: Output) -> tuple[Pos, Key]:
pos += 2 # Skip "[["
pos = skip_chars(src, pos, TOML_WS)
pos, key = parse_key(src, pos)
- if state.flags.is_(key, Flags.FROZEN):
- raise suffixed_err(src, pos, f"Can not mutate immutable namespace {key}")
+ if out.flags.is_(key, Flags.FROZEN):
+ raise suffixed_err(src, pos, f"Cannot mutate immutable namespace {key}")
# Free the namespace now that it points to another empty list item...
- state.flags.unset_all(key)
+ out.flags.unset_all(key)
# ...but this key precisely is still prohibited from table declaration
- state.flags.set(key, Flags.EXPLICIT_NEST, recursive=False)
+ out.flags.set(key, Flags.EXPLICIT_NEST, recursive=False)
try:
- state.out.append_nest_to_list(key)
+ out.data.append_nest_to_list(key)
except KeyError:
- raise suffixed_err(src, pos, "Can not overwrite a value")
- state.header_namespace = key
+ raise suffixed_err(src, pos, "Cannot overwrite a value") from None
- end_marker = src[pos : pos + 2]
- if end_marker != "]]":
- raise suffixed_err(
- src,
- pos,
- f'Found "{end_marker!r}" at the end of an array declaration.'
- ' Expected "]]"',
- )
- return pos + 2
+ if not src.startswith("]]", pos):
+ raise suffixed_err(src, pos, "Expected ']]' at the end of an array declaration")
+ return pos + 2, key
-def key_value_rule(src: str, pos: Pos, state: State, parse_float: ParseFloat) -> Pos:
+def key_value_rule(
+ src: str, pos: Pos, out: Output, header: Key, parse_float: ParseFloat
+) -> Pos:
pos, key, value = parse_key_value_pair(src, pos, parse_float)
key_parent, key_stem = key[:-1], key[-1]
- abs_key_parent = state.header_namespace + key_parent
-
- if state.flags.is_(abs_key_parent, Flags.FROZEN):
+ abs_key_parent = header + key_parent
+
+ relative_path_cont_keys = (header + key[:i] for i in range(1, len(key)))
+ for cont_key in relative_path_cont_keys:
+ # Check that dotted key syntax does not redefine an existing table
+ if out.flags.is_(cont_key, Flags.EXPLICIT_NEST):
+ raise suffixed_err(src, pos, f"Cannot redefine namespace {cont_key}")
+ # Containers in the relative path can't be opened with the table syntax or
+ # dotted key/value syntax in following table sections.
+ out.flags.add_pending(cont_key, Flags.EXPLICIT_NEST)
+
+ if out.flags.is_(abs_key_parent, Flags.FROZEN):
raise suffixed_err(
- src, pos, f"Can not mutate immutable namespace {abs_key_parent}"
+ src, pos, f"Cannot mutate immutable namespace {abs_key_parent}"
)
- # Containers in the relative path can't be opened with the table syntax after this
- state.flags.set_for_relative_key(state.header_namespace, key, Flags.EXPLICIT_NEST)
+
try:
- nest = state.out.get_or_create_nest(abs_key_parent)
+ nest = out.data.get_or_create_nest(abs_key_parent)
except KeyError:
- raise suffixed_err(src, pos, "Can not overwrite a value")
+ raise suffixed_err(src, pos, "Cannot overwrite a value") from None
if key_stem in nest:
- raise suffixed_err(src, pos, "Can not overwrite a value")
+ raise suffixed_err(src, pos, "Cannot overwrite a value")
# Mark inline table and array namespaces recursively immutable
if isinstance(value, (dict, list)):
- abs_key = state.header_namespace + key
- state.flags.set(abs_key, Flags.FROZEN, recursive=True)
+ out.flags.set(header + key, Flags.FROZEN, recursive=True)
nest[key_stem] = value
return pos
def parse_key_value_pair(
src: str, pos: Pos, parse_float: ParseFloat
-) -> Tuple[Pos, Key, Any]:
+) -> tuple[Pos, Key, Any]:
pos, key = parse_key(src, pos)
try:
- char: Optional[str] = src[pos]
+ char: str | None = src[pos]
except IndexError:
char = None
if char != "=":
- raise suffixed_err(src, pos, 'Expected "=" after a key in a key/value pair')
+ raise suffixed_err(src, pos, "Expected '=' after a key in a key/value pair")
pos += 1
pos = skip_chars(src, pos, TOML_WS)
pos, value = parse_value(src, pos, parse_float)
return pos, key, value
-def parse_key(src: str, pos: Pos) -> Tuple[Pos, Key]:
+def parse_key(src: str, pos: Pos) -> tuple[Pos, Key]:
pos, key_part = parse_key_part(src, pos)
- key = [key_part]
+ key: Key = (key_part,)
pos = skip_chars(src, pos, TOML_WS)
while True:
try:
- char: Optional[str] = src[pos]
+ char: str | None = src[pos]
except IndexError:
char = None
if char != ".":
- return pos, tuple(key)
+ return pos, key
pos += 1
pos = skip_chars(src, pos, TOML_WS)
pos, key_part = parse_key_part(src, pos)
- key.append(key_part)
+ key += (key_part,)
pos = skip_chars(src, pos, TOML_WS)
-def parse_key_part(src: str, pos: Pos) -> Tuple[Pos, str]:
+def parse_key_part(src: str, pos: Pos) -> tuple[Pos, str]:
try:
- char: Optional[str] = src[pos]
+ char: str | None = src[pos]
except IndexError:
char = None
if char in BARE_KEY_CHARS:
@@ -417,17 +404,17 @@ def parse_key_part(src: str, pos: Pos) -> Tuple[Pos, str]:
raise suffixed_err(src, pos, "Invalid initial character for a key part")
-def parse_one_line_basic_str(src: str, pos: Pos) -> Tuple[Pos, str]:
+def parse_one_line_basic_str(src: str, pos: Pos) -> tuple[Pos, str]:
pos += 1
return parse_basic_str(src, pos, multiline=False)
-def parse_array(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, list]:
+def parse_array(src: str, pos: Pos, parse_float: ParseFloat) -> tuple[Pos, list]:
pos += 1
array: list = []
pos = skip_comments_and_array_ws(src, pos)
- if src[pos : pos + 1] == "]":
+ if src.startswith("]", pos):
return pos + 1, array
while True:
pos, val = parse_value(src, pos, parse_float)
@@ -442,29 +429,29 @@ def parse_array(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, list]
pos += 1
pos = skip_comments_and_array_ws(src, pos)
- if src[pos : pos + 1] == "]":
+ if src.startswith("]", pos):
return pos + 1, array
-def parse_inline_table(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, dict]:
+def parse_inline_table(src: str, pos: Pos, parse_float: ParseFloat) -> tuple[Pos, dict]:
pos += 1
nested_dict = NestedDict()
flags = Flags()
pos = skip_chars(src, pos, TOML_WS)
- if src[pos : pos + 1] == "}":
+ if src.startswith("}", pos):
return pos + 1, nested_dict.dict
while True:
pos, key, value = parse_key_value_pair(src, pos, parse_float)
key_parent, key_stem = key[:-1], key[-1]
if flags.is_(key, Flags.FROZEN):
- raise suffixed_err(src, pos, f"Can not mutate immutable namespace {key}")
+ raise suffixed_err(src, pos, f"Cannot mutate immutable namespace {key}")
try:
nest = nested_dict.get_or_create_nest(key_parent, access_lists=False)
except KeyError:
- raise suffixed_err(src, pos, "Can not overwrite a value")
+ raise suffixed_err(src, pos, "Cannot overwrite a value") from None
if key_stem in nest:
- raise suffixed_err(src, pos, f'Duplicate inline table key "{key_stem}"')
+ raise suffixed_err(src, pos, f"Duplicate inline table key {key_stem!r}")
nest[key_stem] = value
pos = skip_chars(src, pos, TOML_WS)
c = src[pos : pos + 1]
@@ -480,7 +467,7 @@ def parse_inline_table(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos
def parse_basic_str_escape(
src: str, pos: Pos, *, multiline: bool = False
-) -> Tuple[Pos, str]:
+) -> tuple[Pos, str]:
escape_id = src[pos : pos + 2]
pos += 2
if multiline and escape_id in {"\\ ", "\\\t", "\\\n"}:
@@ -488,11 +475,12 @@ def parse_basic_str_escape(
# the doc. Error if non-whitespace is found before newline.
if escape_id != "\\\n":
pos = skip_chars(src, pos, TOML_WS)
- char = src[pos : pos + 1]
- if not char:
+ try:
+ char = src[pos]
+ except IndexError:
return pos, ""
if char != "\n":
- raise suffixed_err(src, pos, 'Unescaped "\\" in a string')
+ raise suffixed_err(src, pos, "Unescaped '\\' in a string")
pos += 1
pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE)
return pos, ""
@@ -503,18 +491,16 @@ def parse_basic_str_escape(
try:
return pos, BASIC_STR_ESCAPE_REPLACEMENTS[escape_id]
except KeyError:
- if len(escape_id) != 2:
- raise suffixed_err(src, pos, "Unterminated string")
- raise suffixed_err(src, pos, 'Unescaped "\\" in a string')
+ raise suffixed_err(src, pos, "Unescaped '\\' in a string") from None
-def parse_basic_str_escape_multiline(src: str, pos: Pos) -> Tuple[Pos, str]:
+def parse_basic_str_escape_multiline(src: str, pos: Pos) -> tuple[Pos, str]:
return parse_basic_str_escape(src, pos, multiline=True)
-def parse_hex_char(src: str, pos: Pos, hex_len: int) -> Tuple[Pos, str]:
+def parse_hex_char(src: str, pos: Pos, hex_len: int) -> tuple[Pos, str]:
hex_str = src[pos : pos + hex_len]
- if len(hex_str) != hex_len or any(c not in string.hexdigits for c in hex_str):
+ if len(hex_str) != hex_len or not HEXDIGIT_CHARS.issuperset(hex_str):
raise suffixed_err(src, pos, "Invalid hex value")
pos += hex_len
hex_int = int(hex_str, 16)
@@ -523,7 +509,7 @@ def parse_hex_char(src: str, pos: Pos, hex_len: int) -> Tuple[Pos, str]:
return pos, chr(hex_int)
-def parse_literal_str(src: str, pos: Pos) -> Tuple[Pos, str]:
+def parse_literal_str(src: str, pos: Pos) -> tuple[Pos, str]:
pos += 1 # Skip starting apostrophe
start_pos = pos
pos = skip_until(
@@ -532,9 +518,9 @@ def parse_literal_str(src: str, pos: Pos) -> Tuple[Pos, str]:
return pos + 1, src[start_pos:pos] # Skip ending apostrophe
-def parse_multiline_str(src: str, pos: Pos, *, literal: bool) -> Tuple[Pos, str]:
+def parse_multiline_str(src: str, pos: Pos, *, literal: bool) -> tuple[Pos, str]:
pos += 3
- if src[pos : pos + 1] == "\n":
+ if src.startswith("\n", pos):
pos += 1
if literal:
@@ -554,16 +540,16 @@ def parse_multiline_str(src: str, pos: Pos, *, literal: bool) -> Tuple[Pos, str]
# Add at maximum two extra apostrophes/quotes if the end sequence
# is 4 or 5 chars long instead of just 3.
- if src[pos : pos + 1] != delim:
+ if not src.startswith(delim, pos):
return pos, result
pos += 1
- if src[pos : pos + 1] != delim:
+ if not src.startswith(delim, pos):
return pos, result + delim
pos += 1
return pos, result + (delim * 2)
-def parse_basic_str(src: str, pos: Pos, *, multiline: bool) -> Tuple[Pos, str]:
+def parse_basic_str(src: str, pos: Pos, *, multiline: bool) -> tuple[Pos, str]:
if multiline:
error_on = ILLEGAL_MULTILINE_BASIC_STR_CHARS
parse_escapes = parse_basic_str_escape_multiline
@@ -576,11 +562,11 @@ def parse_basic_str(src: str, pos: Pos, *, multiline: bool) -> Tuple[Pos, str]:
try:
char = src[pos]
except IndexError:
- raise suffixed_err(src, pos, "Unterminated string")
+ raise suffixed_err(src, pos, "Unterminated string") from None
if char == '"':
if not multiline:
return pos + 1, result + src[start_pos:pos]
- if src[pos + 1 : pos + 3] == '""':
+ if src.startswith('"""', pos):
return pos + 3, result + src[start_pos:pos]
pos += 1
continue
@@ -591,86 +577,67 @@ def parse_basic_str(src: str, pos: Pos, *, multiline: bool) -> Tuple[Pos, str]:
start_pos = pos
continue
if char in error_on:
- raise suffixed_err(src, pos, f'Illegal character "{char!r}"')
+ raise suffixed_err(src, pos, f"Illegal character {char!r}")
pos += 1
-def parse_regex(src: str, pos: Pos, regex: "Pattern") -> Tuple[Pos, str]:
- match = regex.match(src, pos)
- if not match:
- raise suffixed_err(src, pos, "Unexpected sequence")
- return match.end(), match.group()
-
-
def parse_value( # noqa: C901
src: str, pos: Pos, parse_float: ParseFloat
-) -> Tuple[Pos, Any]:
+) -> tuple[Pos, Any]:
try:
- char: Optional[str] = src[pos]
+ char: str | None = src[pos]
except IndexError:
char = None
+ # IMPORTANT: order conditions based on speed of checking and likelihood
+
# Basic strings
if char == '"':
- if src[pos + 1 : pos + 3] == '""':
+ if src.startswith('"""', pos):
return parse_multiline_str(src, pos, literal=False)
return parse_one_line_basic_str(src, pos)
# Literal strings
if char == "'":
- if src[pos + 1 : pos + 3] == "''":
+ if src.startswith("'''", pos):
return parse_multiline_str(src, pos, literal=True)
return parse_literal_str(src, pos)
# Booleans
if char == "t":
- if src[pos + 1 : pos + 4] == "rue":
+ if src.startswith("true", pos):
return pos + 4, True
if char == "f":
- if src[pos + 1 : pos + 5] == "alse":
+ if src.startswith("false", pos):
return pos + 5, False
+ # Arrays
+ if char == "[":
+ return parse_array(src, pos, parse_float)
+
+ # Inline tables
+ if char == "{":
+ return parse_inline_table(src, pos, parse_float)
+
# Dates and times
datetime_match = RE_DATETIME.match(src, pos)
if datetime_match:
try:
datetime_obj = match_to_datetime(datetime_match)
- except ValueError:
- raise suffixed_err(src, pos, "Invalid date or datetime")
+ except ValueError as e:
+ raise suffixed_err(src, pos, "Invalid date or datetime") from e
return datetime_match.end(), datetime_obj
localtime_match = RE_LOCALTIME.match(src, pos)
if localtime_match:
return localtime_match.end(), match_to_localtime(localtime_match)
- # Non-decimal integers
- if char == "0":
- second_char = src[pos + 1 : pos + 2]
- if second_char == "x":
- pos, hex_str = parse_regex(src, pos + 2, RE_HEX)
- return pos, int(hex_str, 16)
- if second_char == "o":
- pos, oct_str = parse_regex(src, pos + 2, RE_OCT)
- return pos, int(oct_str, 8)
- if second_char == "b":
- pos, bin_str = parse_regex(src, pos + 2, RE_BIN)
- return pos, int(bin_str, 2)
-
- # Decimal integers and "normal" floats.
+ # Integers and "normal" floats.
# The regex will greedily match any type starting with a decimal
- # char, so needs to be located after handling of non-decimal ints,
- # and dates and times.
+ # char, so needs to be located after handling of dates and times.
number_match = RE_NUMBER.match(src, pos)
if number_match:
return number_match.end(), match_to_number(number_match, parse_float)
- # Arrays
- if char == "[":
- return parse_array(src, pos, parse_float)
-
- # Inline tables
- if char == "{":
- return parse_inline_table(src, pos, parse_float)
-
# Special floats
first_three = src[pos : pos + 3]
if first_three in {"inf", "nan"}:
@@ -701,3 +668,24 @@ def suffixed_err(src: str, pos: Pos, msg: str) -> TOMLDecodeError:
def is_unicode_scalar_value(codepoint: int) -> bool:
return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111)
+
+
+def make_safe_parse_float(parse_float: ParseFloat) -> ParseFloat:
+ """A decorator to make `parse_float` safe.
+
+ `parse_float` must not return dicts or lists, because these types
+ would be mixed with parsed TOML tables and arrays, thus confusing
+ the parser. The returned decorated callable raises `ValueError`
+ instead of returning illegal types.
+ """
+ # The default `float` callable never returns illegal types. Optimize it.
+ if parse_float is float: # type: ignore[comparison-overlap]
+ return float
+
+ def safe_parse_float(float_str: str) -> Any:
+ float_value = parse_float(float_str)
+ if isinstance(float_value, (dict, list)):
+ raise ValueError("parse_float must not return dicts or lists")
+ return float_value
+
+ return safe_parse_float
diff --git a/src/pip/_vendor/tomli/_re.py b/src/pip/_vendor/tomli/_re.py
index 3883fdd9c..994bb7493 100644
--- a/src/pip/_vendor/tomli/_re.py
+++ b/src/pip/_vendor/tomli/_re.py
@@ -1,37 +1,55 @@
+# SPDX-License-Identifier: MIT
+# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
+# Licensed to PSF under a Contributor Agreement.
+
+from __future__ import annotations
+
from datetime import date, datetime, time, timedelta, timezone, tzinfo
+from functools import lru_cache
import re
-from typing import TYPE_CHECKING, Any, Optional, Union
+from typing import Any
-if TYPE_CHECKING:
- from re import Match
-
- from pip._vendor.tomli._parser import ParseFloat
+from ._types import ParseFloat
# E.g.
# - 00:32:00.999999
# - 00:32:00
-_TIME_RE_STR = r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?"
+_TIME_RE_STR = r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\.([0-9]{1,6})[0-9]*)?"
-RE_HEX = re.compile(r"[0-9A-Fa-f](?:_?[0-9A-Fa-f])*")
-RE_BIN = re.compile(r"[01](?:_?[01])*")
-RE_OCT = re.compile(r"[0-7](?:_?[0-7])*")
RE_NUMBER = re.compile(
- r"[+-]?(?:0|[1-9](?:_?[0-9])*)" # integer
- + r"(?:\.[0-9](?:_?[0-9])*)?" # optional fractional part
- + r"(?:[eE][+-]?[0-9](?:_?[0-9])*)?" # optional exponent part
+ r"""
+0
+(?:
+ x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex
+ |
+ b[01](?:_?[01])* # bin
+ |
+ o[0-7](?:_?[0-7])* # oct
+)
+|
+[+-]?(?:0|[1-9](?:_?[0-9])*) # dec, integer part
+(?P<floatpart>
+ (?:\.[0-9](?:_?[0-9])*)? # optional fractional part
+ (?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part
+)
+""",
+ flags=re.VERBOSE,
)
RE_LOCALTIME = re.compile(_TIME_RE_STR)
RE_DATETIME = re.compile(
- r"([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-9]|3[01])" # date, e.g. 1988-10-27
- + r"(?:"
- + r"[T ]"
- + _TIME_RE_STR
- + r"(?:(Z)|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))?" # time offset
- + r")?"
+ rf"""
+([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27
+(?:
+ [Tt ]
+ {_TIME_RE_STR}
+ (?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset
+)?
+""",
+ flags=re.VERBOSE,
)
-def match_to_datetime(match: "Match") -> Union[datetime, date]:
+def match_to_datetime(match: re.Match) -> datetime | date:
"""Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`.
Raises ValueError if the match does not correspond to a valid date
@@ -46,7 +64,7 @@ def match_to_datetime(match: "Match") -> Union[datetime, date]:
sec_str,
micros_str,
zulu_time,
- offset_dir_str,
+ offset_sign_str,
offset_hour_str,
offset_minute_str,
) = match.groups()
@@ -54,14 +72,10 @@ def match_to_datetime(match: "Match") -> Union[datetime, date]:
if hour_str is None:
return date(year, month, day)
hour, minute, sec = int(hour_str), int(minute_str), int(sec_str)
- micros = int(micros_str[1:].ljust(6, "0")[:6]) if micros_str else 0
- if offset_dir_str:
- offset_dir = 1 if offset_dir_str == "+" else -1
- tz: Optional[tzinfo] = timezone(
- timedelta(
- hours=offset_dir * int(offset_hour_str),
- minutes=offset_dir * int(offset_minute_str),
- )
+ micros = int(micros_str.ljust(6, "0")) if micros_str else 0
+ if offset_sign_str:
+ tz: tzinfo | None = cached_tz(
+ offset_hour_str, offset_minute_str, offset_sign_str
)
elif zulu_time:
tz = timezone.utc
@@ -70,14 +84,24 @@ def match_to_datetime(match: "Match") -> Union[datetime, date]:
return datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz)
-def match_to_localtime(match: "Match") -> time:
+@lru_cache(maxsize=None)
+def cached_tz(hour_str: str, minute_str: str, sign_str: str) -> timezone:
+ sign = 1 if sign_str == "+" else -1
+ return timezone(
+ timedelta(
+ hours=sign * int(hour_str),
+ minutes=sign * int(minute_str),
+ )
+ )
+
+
+def match_to_localtime(match: re.Match) -> time:
hour_str, minute_str, sec_str, micros_str = match.groups()
- micros = int(micros_str[1:].ljust(6, "0")[:6]) if micros_str else 0
+ micros = int(micros_str.ljust(6, "0")) if micros_str else 0
return time(int(hour_str), int(minute_str), int(sec_str), micros)
-def match_to_number(match: "Match", parse_float: "ParseFloat") -> Any:
- match_str = match.group()
- if "." in match_str or "e" in match_str or "E" in match_str:
- return parse_float(match_str)
- return int(match_str)
+def match_to_number(match: re.Match, parse_float: ParseFloat) -> Any:
+ if match.group("floatpart"):
+ return parse_float(match.group())
+ return int(match.group(), 0)
diff --git a/src/pip/_vendor/tomli/_types.py b/src/pip/_vendor/tomli/_types.py
new file mode 100644
index 000000000..d949412e0
--- /dev/null
+++ b/src/pip/_vendor/tomli/_types.py
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: MIT
+# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
+# Licensed to PSF under a Contributor Agreement.
+
+from typing import Any, Callable, Tuple
+
+# Type annotations
+ParseFloat = Callable[[str], Any]
+Key = Tuple[str, ...]
+Pos = int
diff --git a/src/pip/_vendor/typing_extensions.LICENSE b/src/pip/_vendor/typing_extensions.LICENSE
new file mode 100644
index 000000000..1df6b3b8d
--- /dev/null
+++ b/src/pip/_vendor/typing_extensions.LICENSE
@@ -0,0 +1,254 @@
+A. HISTORY OF THE SOFTWARE
+==========================
+
+Python was created in the early 1990s by Guido van Rossum at Stichting
+Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
+as a successor of a language called ABC. Guido remains Python's
+principal author, although it includes many contributions from others.
+
+In 1995, Guido continued his work on Python at the Corporation for
+National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
+in Reston, Virginia where he released several versions of the
+software.
+
+In May 2000, Guido and the Python core development team moved to
+BeOpen.com to form the BeOpen PythonLabs team. In October of the same
+year, the PythonLabs team moved to Digital Creations, which became
+Zope Corporation. In 2001, the Python Software Foundation (PSF, see
+https://www.python.org/psf/) was formed, a non-profit organization
+created specifically to own Python-related Intellectual Property.
+Zope Corporation was a sponsoring member of the PSF.
+
+All Python releases are Open Source (see http://www.opensource.org for
+the Open Source Definition). Historically, most, but not all, Python
+releases have also been GPL-compatible; the table below summarizes
+the various releases.
+
+ Release Derived Year Owner GPL-
+ from compatible? (1)
+
+ 0.9.0 thru 1.2 1991-1995 CWI yes
+ 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
+ 1.6 1.5.2 2000 CNRI no
+ 2.0 1.6 2000 BeOpen.com no
+ 1.6.1 1.6 2001 CNRI yes (2)
+ 2.1 2.0+1.6.1 2001 PSF no
+ 2.0.1 2.0+1.6.1 2001 PSF yes
+ 2.1.1 2.1+2.0.1 2001 PSF yes
+ 2.1.2 2.1.1 2002 PSF yes
+ 2.1.3 2.1.2 2002 PSF yes
+ 2.2 and above 2.1.1 2001-now PSF yes
+
+Footnotes:
+
+(1) GPL-compatible doesn't mean that we're distributing Python under
+ the GPL. All Python licenses, unlike the GPL, let you distribute
+ a modified version without making your changes open source. The
+ GPL-compatible licenses make it possible to combine Python with
+ other software that is released under the GPL; the others don't.
+
+(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
+ because its license has a choice of law clause. According to
+ CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
+ is "not incompatible" with the GPL.
+
+Thanks to the many outside volunteers who have worked under Guido's
+direction to make these releases possible.
+
+
+B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
+===============================================================
+
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+analyze, test, perform and/or display publicly, prepare derivative works,
+distribute, and otherwise use Python alone or in any derivative version,
+provided, however, that PSF's License Agreement and PSF's notice of copyright,
+i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Python Software Foundation;
+All Rights Reserved" are retained in Python alone or in any derivative version
+prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee. This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
+-------------------------------------------
+
+BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
+
+1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
+office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
+Individual or Organization ("Licensee") accessing and otherwise using
+this software in source or binary form and its associated
+documentation ("the Software").
+
+2. Subject to the terms and conditions of this BeOpen Python License
+Agreement, BeOpen hereby grants Licensee a non-exclusive,
+royalty-free, world-wide license to reproduce, analyze, test, perform
+and/or display publicly, prepare derivative works, distribute, and
+otherwise use the Software alone or in any derivative version,
+provided, however, that the BeOpen Python License is retained in the
+Software, alone or in any derivative version prepared by Licensee.
+
+3. BeOpen is making the Software available to Licensee on an "AS IS"
+basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
+SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
+AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
+DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+5. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+6. This License Agreement shall be governed by and interpreted in all
+respects by the law of the State of California, excluding conflict of
+law provisions. Nothing in this License Agreement shall be deemed to
+create any relationship of agency, partnership, or joint venture
+between BeOpen and Licensee. This License Agreement does not grant
+permission to use BeOpen trademarks or trade names in a trademark
+sense to endorse or promote products or services of Licensee, or any
+third party. As an exception, the "BeOpen Python" logos available at
+http://www.pythonlabs.com/logos.html may be used according to the
+permissions granted on that web page.
+
+7. By copying, installing or otherwise using the software, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
+---------------------------------------
+
+1. This LICENSE AGREEMENT is between the Corporation for National
+Research Initiatives, having an office at 1895 Preston White Drive,
+Reston, VA 20191 ("CNRI"), and the Individual or Organization
+("Licensee") accessing and otherwise using Python 1.6.1 software in
+source or binary form and its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, CNRI
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
+license to reproduce, analyze, test, perform and/or display publicly,
+prepare derivative works, distribute, and otherwise use Python 1.6.1
+alone or in any derivative version, provided, however, that CNRI's
+License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
+1995-2001 Corporation for National Research Initiatives; All Rights
+Reserved" are retained in Python 1.6.1 alone or in any derivative
+version prepared by Licensee. Alternately, in lieu of CNRI's License
+Agreement, Licensee may substitute the following text (omitting the
+quotes): "Python 1.6.1 is made available subject to the terms and
+conditions in CNRI's License Agreement. This Agreement together with
+Python 1.6.1 may be located on the internet using the following
+unique, persistent identifier (known as a handle): 1895.22/1013. This
+Agreement may also be obtained from a proxy server on the internet
+using the following URL: http://hdl.handle.net/1895.22/1013".
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python 1.6.1 or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python 1.6.1.
+
+4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
+basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. This License Agreement shall be governed by the federal
+intellectual property law of the United States, including without
+limitation the federal copyright law, and, to the extent such
+U.S. federal law does not apply, by the law of the Commonwealth of
+Virginia, excluding Virginia's conflict of law provisions.
+Notwithstanding the foregoing, with regard to derivative works based
+on Python 1.6.1 that incorporate non-separable material that was
+previously distributed under the GNU General Public License (GPL), the
+law of the Commonwealth of Virginia shall govern this License
+Agreement only as to issues arising under or with respect to
+Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
+License Agreement shall be deemed to create any relationship of
+agency, partnership, or joint venture between CNRI and Licensee. This
+License Agreement does not grant permission to use CNRI trademarks or
+trade name in a trademark sense to endorse or promote products or
+services of Licensee, or any third party.
+
+8. By clicking on the "ACCEPT" button where indicated, or by copying,
+installing or otherwise using Python 1.6.1, Licensee agrees to be
+bound by the terms and conditions of this License Agreement.
+
+ ACCEPT
+
+
+CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
+--------------------------------------------------
+
+Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
+The Netherlands. All rights reserved.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the name of Stichting Mathematisch
+Centrum or CWI not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior
+permission.
+
+STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
+FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/src/pip/_vendor/typing_extensions.py b/src/pip/_vendor/typing_extensions.py
new file mode 100644
index 000000000..34199c2a9
--- /dev/null
+++ b/src/pip/_vendor/typing_extensions.py
@@ -0,0 +1,2209 @@
+import abc
+import collections
+import collections.abc
+import functools
+import operator
+import sys
+import types as _types
+import typing
+
+
+__all__ = [
+ # Super-special typing primitives.
+ 'Any',
+ 'ClassVar',
+ 'Concatenate',
+ 'Final',
+ 'LiteralString',
+ 'ParamSpec',
+ 'ParamSpecArgs',
+ 'ParamSpecKwargs',
+ 'Self',
+ 'Type',
+ 'TypeVar',
+ 'TypeVarTuple',
+ 'Unpack',
+
+ # ABCs (from collections.abc).
+ 'Awaitable',
+ 'AsyncIterator',
+ 'AsyncIterable',
+ 'Coroutine',
+ 'AsyncGenerator',
+ 'AsyncContextManager',
+ 'ChainMap',
+
+ # Concrete collection types.
+ 'ContextManager',
+ 'Counter',
+ 'Deque',
+ 'DefaultDict',
+ 'NamedTuple',
+ 'OrderedDict',
+ 'TypedDict',
+
+ # Structural checks, a.k.a. protocols.
+ 'SupportsIndex',
+
+ # One-off things.
+ 'Annotated',
+ 'assert_never',
+ 'assert_type',
+ 'clear_overloads',
+ 'dataclass_transform',
+ 'get_overloads',
+ 'final',
+ 'get_args',
+ 'get_origin',
+ 'get_type_hints',
+ 'IntVar',
+ 'is_typeddict',
+ 'Literal',
+ 'NewType',
+ 'overload',
+ 'override',
+ 'Protocol',
+ 'reveal_type',
+ 'runtime',
+ 'runtime_checkable',
+ 'Text',
+ 'TypeAlias',
+ 'TypeGuard',
+ 'TYPE_CHECKING',
+ 'Never',
+ 'NoReturn',
+ 'Required',
+ 'NotRequired',
+]
+
+# for backward compatibility
+PEP_560 = True
+GenericMeta = type
+
+# The functions below are modified copies of typing internal helpers.
+# They are needed by _ProtocolMeta and they provide support for PEP 646.
+
+_marker = object()
+
+
+def _check_generic(cls, parameters, elen=_marker):
+ """Check correct count for parameters of a generic cls (internal helper).
+ This gives a nice error message in case of count mismatch.
+ """
+ if not elen:
+ raise TypeError(f"{cls} is not a generic class")
+ if elen is _marker:
+ if not hasattr(cls, "__parameters__") or not cls.__parameters__:
+ raise TypeError(f"{cls} is not a generic class")
+ elen = len(cls.__parameters__)
+ alen = len(parameters)
+ if alen != elen:
+ if hasattr(cls, "__parameters__"):
+ parameters = [p for p in cls.__parameters__ if not _is_unpack(p)]
+ num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters)
+ if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples):
+ return
+ raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};"
+ f" actual {alen}, expected {elen}")
+
+
+if sys.version_info >= (3, 10):
+ def _should_collect_from_parameters(t):
+ return isinstance(
+ t, (typing._GenericAlias, _types.GenericAlias, _types.UnionType)
+ )
+elif sys.version_info >= (3, 9):
+ def _should_collect_from_parameters(t):
+ return isinstance(t, (typing._GenericAlias, _types.GenericAlias))
+else:
+ def _should_collect_from_parameters(t):
+ return isinstance(t, typing._GenericAlias) and not t._special
+
+
+def _collect_type_vars(types, typevar_types=None):
+ """Collect all type variable contained in types in order of
+ first appearance (lexicographic order). For example::
+
+ _collect_type_vars((T, List[S, T])) == (T, S)
+ """
+ if typevar_types is None:
+ typevar_types = typing.TypeVar
+ tvars = []
+ for t in types:
+ if (
+ isinstance(t, typevar_types) and
+ t not in tvars and
+ not _is_unpack(t)
+ ):
+ tvars.append(t)
+ if _should_collect_from_parameters(t):
+ tvars.extend([t for t in t.__parameters__ if t not in tvars])
+ return tuple(tvars)
+
+
+NoReturn = typing.NoReturn
+
+# Some unconstrained type variables. These are used by the container types.
+# (These are not for export.)
+T = typing.TypeVar('T') # Any type.
+KT = typing.TypeVar('KT') # Key type.
+VT = typing.TypeVar('VT') # Value type.
+T_co = typing.TypeVar('T_co', covariant=True) # Any type covariant containers.
+T_contra = typing.TypeVar('T_contra', contravariant=True) # Ditto contravariant.
+
+
+if sys.version_info >= (3, 11):
+ from typing import Any
+else:
+
+ class _AnyMeta(type):
+ def __instancecheck__(self, obj):
+ if self is Any:
+ raise TypeError("typing_extensions.Any cannot be used with isinstance()")
+ return super().__instancecheck__(obj)
+
+ def __repr__(self):
+ if self is Any:
+ return "typing_extensions.Any"
+ return super().__repr__()
+
+ class Any(metaclass=_AnyMeta):
+ """Special type indicating an unconstrained type.
+ - Any is compatible with every type.
+ - Any assumed to have all methods.
+ - All values assumed to be instances of Any.
+ Note that all the above statements are true from the point of view of
+ static type checkers. At runtime, Any should not be used with instance
+ checks.
+ """
+ def __new__(cls, *args, **kwargs):
+ if cls is Any:
+ raise TypeError("Any cannot be instantiated")
+ return super().__new__(cls, *args, **kwargs)
+
+
+ClassVar = typing.ClassVar
+
+# On older versions of typing there is an internal class named "Final".
+# 3.8+
+if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7):
+ Final = typing.Final
+# 3.7
+else:
+ class _FinalForm(typing._SpecialForm, _root=True):
+
+ def __repr__(self):
+ return 'typing_extensions.' + self._name
+
+ def __getitem__(self, parameters):
+ item = typing._type_check(parameters,
+ f'{self._name} accepts only a single type.')
+ return typing._GenericAlias(self, (item,))
+
+ Final = _FinalForm('Final',
+ doc="""A special typing construct to indicate that a name
+ cannot be re-assigned or overridden in a subclass.
+ For example:
+
+ MAX_SIZE: Final = 9000
+ MAX_SIZE += 1 # Error reported by type checker
+
+ class Connection:
+ TIMEOUT: Final[int] = 10
+ class FastConnector(Connection):
+ TIMEOUT = 1 # Error reported by type checker
+
+ There is no runtime checking of these properties.""")
+
+if sys.version_info >= (3, 11):
+ final = typing.final
+else:
+ # @final exists in 3.8+, but we backport it for all versions
+ # before 3.11 to keep support for the __final__ attribute.
+ # See https://bugs.python.org/issue46342
+ def final(f):
+ """This decorator can be used to indicate to type checkers that
+ the decorated method cannot be overridden, and decorated class
+ cannot be subclassed. For example:
+
+ class Base:
+ @final
+ def done(self) -> None:
+ ...
+ class Sub(Base):
+ def done(self) -> None: # Error reported by type checker
+ ...
+ @final
+ class Leaf:
+ ...
+ class Other(Leaf): # Error reported by type checker
+ ...
+
+ There is no runtime checking of these properties. The decorator
+ sets the ``__final__`` attribute to ``True`` on the decorated object
+ to allow runtime introspection.
+ """
+ try:
+ f.__final__ = True
+ except (AttributeError, TypeError):
+ # Skip the attribute silently if it is not writable.
+ # AttributeError happens if the object has __slots__ or a
+ # read-only property, TypeError if it's a builtin class.
+ pass
+ return f
+
+
+def IntVar(name):
+ return typing.TypeVar(name)
+
+
+# 3.8+:
+if hasattr(typing, 'Literal'):
+ Literal = typing.Literal
+# 3.7:
+else:
+ class _LiteralForm(typing._SpecialForm, _root=True):
+
+ def __repr__(self):
+ return 'typing_extensions.' + self._name
+
+ def __getitem__(self, parameters):
+ return typing._GenericAlias(self, parameters)
+
+ Literal = _LiteralForm('Literal',
+ doc="""A type that can be used to indicate to type checkers
+ that the corresponding value has a value literally equivalent
+ to the provided parameter. For example:
+
+ var: Literal[4] = 4
+
+ The type checker understands that 'var' is literally equal to
+ the value 4 and no other value.
+
+ Literal[...] cannot be subclassed. There is no runtime
+ checking verifying that the parameter is actually a value
+ instead of a type.""")
+
+
+_overload_dummy = typing._overload_dummy # noqa
+
+
+if hasattr(typing, "get_overloads"): # 3.11+
+ overload = typing.overload
+ get_overloads = typing.get_overloads
+ clear_overloads = typing.clear_overloads
+else:
+ # {module: {qualname: {firstlineno: func}}}
+ _overload_registry = collections.defaultdict(
+ functools.partial(collections.defaultdict, dict)
+ )
+
+ def overload(func):
+ """Decorator for overloaded functions/methods.
+
+ In a stub file, place two or more stub definitions for the same
+ function in a row, each decorated with @overload. For example:
+
+ @overload
+ def utf8(value: None) -> None: ...
+ @overload
+ def utf8(value: bytes) -> bytes: ...
+ @overload
+ def utf8(value: str) -> bytes: ...
+
+ In a non-stub file (i.e. a regular .py file), do the same but
+ follow it with an implementation. The implementation should *not*
+ be decorated with @overload. For example:
+
+ @overload
+ def utf8(value: None) -> None: ...
+ @overload
+ def utf8(value: bytes) -> bytes: ...
+ @overload
+ def utf8(value: str) -> bytes: ...
+ def utf8(value):
+ # implementation goes here
+
+ The overloads for a function can be retrieved at runtime using the
+ get_overloads() function.
+ """
+ # classmethod and staticmethod
+ f = getattr(func, "__func__", func)
+ try:
+ _overload_registry[f.__module__][f.__qualname__][
+ f.__code__.co_firstlineno
+ ] = func
+ except AttributeError:
+ # Not a normal function; ignore.
+ pass
+ return _overload_dummy
+
+ def get_overloads(func):
+ """Return all defined overloads for *func* as a sequence."""
+ # classmethod and staticmethod
+ f = getattr(func, "__func__", func)
+ if f.__module__ not in _overload_registry:
+ return []
+ mod_dict = _overload_registry[f.__module__]
+ if f.__qualname__ not in mod_dict:
+ return []
+ return list(mod_dict[f.__qualname__].values())
+
+ def clear_overloads():
+ """Clear all overloads in the registry."""
+ _overload_registry.clear()
+
+
+# This is not a real generic class. Don't use outside annotations.
+Type = typing.Type
+
+# Various ABCs mimicking those in collections.abc.
+# A few are simply re-exported for completeness.
+
+
+Awaitable = typing.Awaitable
+Coroutine = typing.Coroutine
+AsyncIterable = typing.AsyncIterable
+AsyncIterator = typing.AsyncIterator
+Deque = typing.Deque
+ContextManager = typing.ContextManager
+AsyncContextManager = typing.AsyncContextManager
+DefaultDict = typing.DefaultDict
+
+# 3.7.2+
+if hasattr(typing, 'OrderedDict'):
+ OrderedDict = typing.OrderedDict
+# 3.7.0-3.7.2
+else:
+ OrderedDict = typing._alias(collections.OrderedDict, (KT, VT))
+
+Counter = typing.Counter
+ChainMap = typing.ChainMap
+AsyncGenerator = typing.AsyncGenerator
+NewType = typing.NewType
+Text = typing.Text
+TYPE_CHECKING = typing.TYPE_CHECKING
+
+
+_PROTO_WHITELIST = ['Callable', 'Awaitable',
+ 'Iterable', 'Iterator', 'AsyncIterable', 'AsyncIterator',
+ 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible',
+ 'ContextManager', 'AsyncContextManager']
+
+
+def _get_protocol_attrs(cls):
+ attrs = set()
+ for base in cls.__mro__[:-1]: # without object
+ if base.__name__ in ('Protocol', 'Generic'):
+ continue
+ annotations = getattr(base, '__annotations__', {})
+ for attr in list(base.__dict__.keys()) + list(annotations.keys()):
+ if (not attr.startswith('_abc_') and attr not in (
+ '__abstractmethods__', '__annotations__', '__weakref__',
+ '_is_protocol', '_is_runtime_protocol', '__dict__',
+ '__args__', '__slots__',
+ '__next_in_mro__', '__parameters__', '__origin__',
+ '__orig_bases__', '__extra__', '__tree_hash__',
+ '__doc__', '__subclasshook__', '__init__', '__new__',
+ '__module__', '_MutableMapping__marker', '_gorg')):
+ attrs.add(attr)
+ return attrs
+
+
+def _is_callable_members_only(cls):
+ return all(callable(getattr(cls, attr, None)) for attr in _get_protocol_attrs(cls))
+
+
+def _maybe_adjust_parameters(cls):
+ """Helper function used in Protocol.__init_subclass__ and _TypedDictMeta.__new__.
+
+ The contents of this function are very similar
+ to logic found in typing.Generic.__init_subclass__
+ on the CPython main branch.
+ """
+ tvars = []
+ if '__orig_bases__' in cls.__dict__:
+ tvars = typing._collect_type_vars(cls.__orig_bases__)
+ # Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn].
+ # If found, tvars must be a subset of it.
+ # If not found, tvars is it.
+ # Also check for and reject plain Generic,
+ # and reject multiple Generic[...] and/or Protocol[...].
+ gvars = None
+ for base in cls.__orig_bases__:
+ if (isinstance(base, typing._GenericAlias) and
+ base.__origin__ in (typing.Generic, Protocol)):
+ # for error messages
+ the_base = base.__origin__.__name__
+ if gvars is not None:
+ raise TypeError(
+ "Cannot inherit from Generic[...]"
+ " and/or Protocol[...] multiple types.")
+ gvars = base.__parameters__
+ if gvars is None:
+ gvars = tvars
+ else:
+ tvarset = set(tvars)
+ gvarset = set(gvars)
+ if not tvarset <= gvarset:
+ s_vars = ', '.join(str(t) for t in tvars if t not in gvarset)
+ s_args = ', '.join(str(g) for g in gvars)
+ raise TypeError(f"Some type variables ({s_vars}) are"
+ f" not listed in {the_base}[{s_args}]")
+ tvars = gvars
+ cls.__parameters__ = tuple(tvars)
+
+
+# 3.8+
+if hasattr(typing, 'Protocol'):
+ Protocol = typing.Protocol
+# 3.7
+else:
+
+ def _no_init(self, *args, **kwargs):
+ if type(self)._is_protocol:
+ raise TypeError('Protocols cannot be instantiated')
+
+ class _ProtocolMeta(abc.ABCMeta): # noqa: B024
+ # This metaclass is a bit unfortunate and exists only because of the lack
+ # of __instancehook__.
+ def __instancecheck__(cls, instance):
+ # We need this method for situations where attributes are
+ # assigned in __init__.
+ if ((not getattr(cls, '_is_protocol', False) or
+ _is_callable_members_only(cls)) and
+ issubclass(instance.__class__, cls)):
+ return True
+ if cls._is_protocol:
+ if all(hasattr(instance, attr) and
+ (not callable(getattr(cls, attr, None)) or
+ getattr(instance, attr) is not None)
+ for attr in _get_protocol_attrs(cls)):
+ return True
+ return super().__instancecheck__(instance)
+
+ class Protocol(metaclass=_ProtocolMeta):
+ # There is quite a lot of overlapping code with typing.Generic.
+ # Unfortunately it is hard to avoid this while these live in two different
+ # modules. The duplicated code will be removed when Protocol is moved to typing.
+ """Base class for protocol classes. Protocol classes are defined as::
+
+ class Proto(Protocol):
+ def meth(self) -> int:
+ ...
+
+ Such classes are primarily used with static type checkers that recognize
+ structural subtyping (static duck-typing), for example::
+
+ class C:
+ def meth(self) -> int:
+ return 0
+
+ def func(x: Proto) -> int:
+ return x.meth()
+
+ func(C()) # Passes static type check
+
+ See PEP 544 for details. Protocol classes decorated with
+ @typing_extensions.runtime act as simple-minded runtime protocol that checks
+ only the presence of given attributes, ignoring their type signatures.
+
+ Protocol classes can be generic, they are defined as::
+
+ class GenProto(Protocol[T]):
+ def meth(self) -> T:
+ ...
+ """
+ __slots__ = ()
+ _is_protocol = True
+
+ def __new__(cls, *args, **kwds):
+ if cls is Protocol:
+ raise TypeError("Type Protocol cannot be instantiated; "
+ "it can only be used as a base class")
+ return super().__new__(cls)
+
+ @typing._tp_cache
+ def __class_getitem__(cls, params):
+ if not isinstance(params, tuple):
+ params = (params,)
+ if not params and cls is not typing.Tuple:
+ raise TypeError(
+ f"Parameter list to {cls.__qualname__}[...] cannot be empty")
+ msg = "Parameters to generic types must be types."
+ params = tuple(typing._type_check(p, msg) for p in params) # noqa
+ if cls is Protocol:
+ # Generic can only be subscripted with unique type variables.
+ if not all(isinstance(p, typing.TypeVar) for p in params):
+ i = 0
+ while isinstance(params[i], typing.TypeVar):
+ i += 1
+ raise TypeError(
+ "Parameters to Protocol[...] must all be type variables."
+ f" Parameter {i + 1} is {params[i]}")
+ if len(set(params)) != len(params):
+ raise TypeError(
+ "Parameters to Protocol[...] must all be unique")
+ else:
+ # Subscripting a regular Generic subclass.
+ _check_generic(cls, params, len(cls.__parameters__))
+ return typing._GenericAlias(cls, params)
+
+ def __init_subclass__(cls, *args, **kwargs):
+ if '__orig_bases__' in cls.__dict__:
+ error = typing.Generic in cls.__orig_bases__
+ else:
+ error = typing.Generic in cls.__bases__
+ if error:
+ raise TypeError("Cannot inherit from plain Generic")
+ _maybe_adjust_parameters(cls)
+
+ # Determine if this is a protocol or a concrete subclass.
+ if not cls.__dict__.get('_is_protocol', None):
+ cls._is_protocol = any(b is Protocol for b in cls.__bases__)
+
+ # Set (or override) the protocol subclass hook.
+ def _proto_hook(other):
+ if not cls.__dict__.get('_is_protocol', None):
+ return NotImplemented
+ if not getattr(cls, '_is_runtime_protocol', False):
+ if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']:
+ return NotImplemented
+ raise TypeError("Instance and class checks can only be used with"
+ " @runtime protocols")
+ if not _is_callable_members_only(cls):
+ if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']:
+ return NotImplemented
+ raise TypeError("Protocols with non-method members"
+ " don't support issubclass()")
+ if not isinstance(other, type):
+ # Same error as for issubclass(1, int)
+ raise TypeError('issubclass() arg 1 must be a class')
+ for attr in _get_protocol_attrs(cls):
+ for base in other.__mro__:
+ if attr in base.__dict__:
+ if base.__dict__[attr] is None:
+ return NotImplemented
+ break
+ annotations = getattr(base, '__annotations__', {})
+ if (isinstance(annotations, typing.Mapping) and
+ attr in annotations and
+ isinstance(other, _ProtocolMeta) and
+ other._is_protocol):
+ break
+ else:
+ return NotImplemented
+ return True
+ if '__subclasshook__' not in cls.__dict__:
+ cls.__subclasshook__ = _proto_hook
+
+ # We have nothing more to do for non-protocols.
+ if not cls._is_protocol:
+ return
+
+ # Check consistency of bases.
+ for base in cls.__bases__:
+ if not (base in (object, typing.Generic) or
+ base.__module__ == 'collections.abc' and
+ base.__name__ in _PROTO_WHITELIST or
+ isinstance(base, _ProtocolMeta) and base._is_protocol):
+ raise TypeError('Protocols can only inherit from other'
+ f' protocols, got {repr(base)}')
+ cls.__init__ = _no_init
+
+
+# 3.8+
+if hasattr(typing, 'runtime_checkable'):
+ runtime_checkable = typing.runtime_checkable
+# 3.7
+else:
+ def runtime_checkable(cls):
+ """Mark a protocol class as a runtime protocol, so that it
+ can be used with isinstance() and issubclass(). Raise TypeError
+ if applied to a non-protocol class.
+
+ This allows a simple-minded structural check very similar to the
+ one-offs in collections.abc such as Hashable.
+ """
+ if not isinstance(cls, _ProtocolMeta) or not cls._is_protocol:
+ raise TypeError('@runtime_checkable can be only applied to protocol classes,'
+ f' got {cls!r}')
+ cls._is_runtime_protocol = True
+ return cls
+
+
+# Exists for backwards compatibility.
+runtime = runtime_checkable
+
+
+# 3.8+
+if hasattr(typing, 'SupportsIndex'):
+ SupportsIndex = typing.SupportsIndex
+# 3.7
+else:
+ @runtime_checkable
+ class SupportsIndex(Protocol):
+ __slots__ = ()
+
+ @abc.abstractmethod
+ def __index__(self) -> int:
+ pass
+
+
+if hasattr(typing, "Required"):
+ # The standard library TypedDict in Python 3.8 does not store runtime information
+ # about which (if any) keys are optional. See https://bugs.python.org/issue38834
+ # The standard library TypedDict in Python 3.9.0/1 does not honour the "total"
+ # keyword with old-style TypedDict(). See https://bugs.python.org/issue42059
+ # The standard library TypedDict below Python 3.11 does not store runtime
+ # information about optional and required keys when using Required or NotRequired.
+ # Generic TypedDicts are also impossible using typing.TypedDict on Python <3.11.
+ TypedDict = typing.TypedDict
+ _TypedDictMeta = typing._TypedDictMeta
+ is_typeddict = typing.is_typeddict
+else:
+ def _check_fails(cls, other):
+ try:
+ if sys._getframe(1).f_globals['__name__'] not in ['abc',
+ 'functools',
+ 'typing']:
+ # Typed dicts are only for static structural subtyping.
+ raise TypeError('TypedDict does not support instance and class checks')
+ except (AttributeError, ValueError):
+ pass
+ return False
+
+ def _dict_new(*args, **kwargs):
+ if not args:
+ raise TypeError('TypedDict.__new__(): not enough arguments')
+ _, args = args[0], args[1:] # allow the "cls" keyword be passed
+ return dict(*args, **kwargs)
+
+ _dict_new.__text_signature__ = '($cls, _typename, _fields=None, /, **kwargs)'
+
+ def _typeddict_new(*args, total=True, **kwargs):
+ if not args:
+ raise TypeError('TypedDict.__new__(): not enough arguments')
+ _, args = args[0], args[1:] # allow the "cls" keyword be passed
+ if args:
+ typename, args = args[0], args[1:] # allow the "_typename" keyword be passed
+ elif '_typename' in kwargs:
+ typename = kwargs.pop('_typename')
+ import warnings
+ warnings.warn("Passing '_typename' as keyword argument is deprecated",
+ DeprecationWarning, stacklevel=2)
+ else:
+ raise TypeError("TypedDict.__new__() missing 1 required positional "
+ "argument: '_typename'")
+ if args:
+ try:
+ fields, = args # allow the "_fields" keyword be passed
+ except ValueError:
+ raise TypeError('TypedDict.__new__() takes from 2 to 3 '
+ f'positional arguments but {len(args) + 2} '
+ 'were given')
+ elif '_fields' in kwargs and len(kwargs) == 1:
+ fields = kwargs.pop('_fields')
+ import warnings
+ warnings.warn("Passing '_fields' as keyword argument is deprecated",
+ DeprecationWarning, stacklevel=2)
+ else:
+ fields = None
+
+ if fields is None:
+ fields = kwargs
+ elif kwargs:
+ raise TypeError("TypedDict takes either a dict or keyword arguments,"
+ " but not both")
+
+ ns = {'__annotations__': dict(fields)}
+ try:
+ # Setting correct module is necessary to make typed dict classes pickleable.
+ ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__')
+ except (AttributeError, ValueError):
+ pass
+
+ return _TypedDictMeta(typename, (), ns, total=total)
+
+ _typeddict_new.__text_signature__ = ('($cls, _typename, _fields=None,'
+ ' /, *, total=True, **kwargs)')
+
+ class _TypedDictMeta(type):
+ def __init__(cls, name, bases, ns, total=True):
+ super().__init__(name, bases, ns)
+
+ def __new__(cls, name, bases, ns, total=True):
+ # Create new typed dict class object.
+ # This method is called directly when TypedDict is subclassed,
+ # or via _typeddict_new when TypedDict is instantiated. This way
+ # TypedDict supports all three syntaxes described in its docstring.
+ # Subclasses and instances of TypedDict return actual dictionaries
+ # via _dict_new.
+ ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new
+ # Don't insert typing.Generic into __bases__ here,
+ # or Generic.__init_subclass__ will raise TypeError
+ # in the super().__new__() call.
+ # Instead, monkey-patch __bases__ onto the class after it's been created.
+ tp_dict = super().__new__(cls, name, (dict,), ns)
+
+ if any(issubclass(base, typing.Generic) for base in bases):
+ tp_dict.__bases__ = (typing.Generic, dict)
+ _maybe_adjust_parameters(tp_dict)
+
+ annotations = {}
+ own_annotations = ns.get('__annotations__', {})
+ msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
+ own_annotations = {
+ n: typing._type_check(tp, msg) for n, tp in own_annotations.items()
+ }
+ required_keys = set()
+ optional_keys = set()
+
+ for base in bases:
+ annotations.update(base.__dict__.get('__annotations__', {}))
+ required_keys.update(base.__dict__.get('__required_keys__', ()))
+ optional_keys.update(base.__dict__.get('__optional_keys__', ()))
+
+ annotations.update(own_annotations)
+ for annotation_key, annotation_type in own_annotations.items():
+ annotation_origin = get_origin(annotation_type)
+ if annotation_origin is Annotated:
+ annotation_args = get_args(annotation_type)
+ if annotation_args:
+ annotation_type = annotation_args[0]
+ annotation_origin = get_origin(annotation_type)
+
+ if annotation_origin is Required:
+ required_keys.add(annotation_key)
+ elif annotation_origin is NotRequired:
+ optional_keys.add(annotation_key)
+ elif total:
+ required_keys.add(annotation_key)
+ else:
+ optional_keys.add(annotation_key)
+
+ tp_dict.__annotations__ = annotations
+ tp_dict.__required_keys__ = frozenset(required_keys)
+ tp_dict.__optional_keys__ = frozenset(optional_keys)
+ if not hasattr(tp_dict, '__total__'):
+ tp_dict.__total__ = total
+ return tp_dict
+
+ __instancecheck__ = __subclasscheck__ = _check_fails
+
+ TypedDict = _TypedDictMeta('TypedDict', (dict,), {})
+ TypedDict.__module__ = __name__
+ TypedDict.__doc__ = \
+ """A simple typed name space. At runtime it is equivalent to a plain dict.
+
+ TypedDict creates a dictionary type that expects all of its
+ instances to have a certain set of keys, with each key
+ associated with a value of a consistent type. This expectation
+ is not checked at runtime but is only enforced by type checkers.
+ Usage::
+
+ class Point2D(TypedDict):
+ x: int
+ y: int
+ label: str
+
+ a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK
+ b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check
+
+ assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first')
+
+ The type info can be accessed via the Point2D.__annotations__ dict, and
+ the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets.
+ TypedDict supports two additional equivalent forms::
+
+ Point2D = TypedDict('Point2D', x=int, y=int, label=str)
+ Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str})
+
+ The class syntax is only supported in Python 3.6+, while two other
+ syntax forms work for Python 2.7 and 3.2+
+ """
+
+ if hasattr(typing, "_TypedDictMeta"):
+ _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta)
+ else:
+ _TYPEDDICT_TYPES = (_TypedDictMeta,)
+
+ def is_typeddict(tp):
+ """Check if an annotation is a TypedDict class
+
+ For example::
+ class Film(TypedDict):
+ title: str
+ year: int
+
+ is_typeddict(Film) # => True
+ is_typeddict(Union[list, str]) # => False
+ """
+ return isinstance(tp, tuple(_TYPEDDICT_TYPES))
+
+
+if hasattr(typing, "assert_type"):
+ assert_type = typing.assert_type
+
+else:
+ def assert_type(__val, __typ):
+ """Assert (to the type checker) that the value is of the given type.
+
+ When the type checker encounters a call to assert_type(), it
+ emits an error if the value is not of the specified type::
+
+ def greet(name: str) -> None:
+ assert_type(name, str) # ok
+ assert_type(name, int) # type checker error
+
+ At runtime this returns the first argument unchanged and otherwise
+ does nothing.
+ """
+ return __val
+
+
+if hasattr(typing, "Required"):
+ get_type_hints = typing.get_type_hints
+else:
+ import functools
+ import types
+
+ # replaces _strip_annotations()
+ def _strip_extras(t):
+ """Strips Annotated, Required and NotRequired from a given type."""
+ if isinstance(t, _AnnotatedAlias):
+ return _strip_extras(t.__origin__)
+ if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired):
+ return _strip_extras(t.__args__[0])
+ if isinstance(t, typing._GenericAlias):
+ stripped_args = tuple(_strip_extras(a) for a in t.__args__)
+ if stripped_args == t.__args__:
+ return t
+ return t.copy_with(stripped_args)
+ if hasattr(types, "GenericAlias") and isinstance(t, types.GenericAlias):
+ stripped_args = tuple(_strip_extras(a) for a in t.__args__)
+ if stripped_args == t.__args__:
+ return t
+ return types.GenericAlias(t.__origin__, stripped_args)
+ if hasattr(types, "UnionType") and isinstance(t, types.UnionType):
+ stripped_args = tuple(_strip_extras(a) for a in t.__args__)
+ if stripped_args == t.__args__:
+ return t
+ return functools.reduce(operator.or_, stripped_args)
+
+ return t
+
+ def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
+ """Return type hints for an object.
+
+ This is often the same as obj.__annotations__, but it handles
+ forward references encoded as string literals, adds Optional[t] if a
+ default value equal to None is set and recursively replaces all
+ 'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T'
+ (unless 'include_extras=True').
+
+ The argument may be a module, class, method, or function. The annotations
+ are returned as a dictionary. For classes, annotations include also
+ inherited members.
+
+ TypeError is raised if the argument is not of a type that can contain
+ annotations, and an empty dictionary is returned if no annotations are
+ present.
+
+ BEWARE -- the behavior of globalns and localns is counterintuitive
+ (unless you are familiar with how eval() and exec() work). The
+ search order is locals first, then globals.
+
+ - If no dict arguments are passed, an attempt is made to use the
+ globals from obj (or the respective module's globals for classes),
+ and these are also used as the locals. If the object does not appear
+ to have globals, an empty dictionary is used.
+
+ - If one dict argument is passed, it is used for both globals and
+ locals.
+
+ - If two dict arguments are passed, they specify globals and
+ locals, respectively.
+ """
+ if hasattr(typing, "Annotated"):
+ hint = typing.get_type_hints(
+ obj, globalns=globalns, localns=localns, include_extras=True
+ )
+ else:
+ hint = typing.get_type_hints(obj, globalns=globalns, localns=localns)
+ if include_extras:
+ return hint
+ return {k: _strip_extras(t) for k, t in hint.items()}
+
+
+# Python 3.9+ has PEP 593 (Annotated)
+if hasattr(typing, 'Annotated'):
+ Annotated = typing.Annotated
+ # Not exported and not a public API, but needed for get_origin() and get_args()
+ # to work.
+ _AnnotatedAlias = typing._AnnotatedAlias
+# 3.7-3.8
+else:
+ class _AnnotatedAlias(typing._GenericAlias, _root=True):
+ """Runtime representation of an annotated type.
+
+ At its core 'Annotated[t, dec1, dec2, ...]' is an alias for the type 't'
+ with extra annotations. The alias behaves like a normal typing alias,
+ instantiating is the same as instantiating the underlying type, binding
+ it to types is also the same.
+ """
+ def __init__(self, origin, metadata):
+ if isinstance(origin, _AnnotatedAlias):
+ metadata = origin.__metadata__ + metadata
+ origin = origin.__origin__
+ super().__init__(origin, origin)
+ self.__metadata__ = metadata
+
+ def copy_with(self, params):
+ assert len(params) == 1
+ new_type = params[0]
+ return _AnnotatedAlias(new_type, self.__metadata__)
+
+ def __repr__(self):
+ return (f"typing_extensions.Annotated[{typing._type_repr(self.__origin__)}, "
+ f"{', '.join(repr(a) for a in self.__metadata__)}]")
+
+ def __reduce__(self):
+ return operator.getitem, (
+ Annotated, (self.__origin__,) + self.__metadata__
+ )
+
+ def __eq__(self, other):
+ if not isinstance(other, _AnnotatedAlias):
+ return NotImplemented
+ if self.__origin__ != other.__origin__:
+ return False
+ return self.__metadata__ == other.__metadata__
+
+ def __hash__(self):
+ return hash((self.__origin__, self.__metadata__))
+
+ class Annotated:
+ """Add context specific metadata to a type.
+
+ Example: Annotated[int, runtime_check.Unsigned] indicates to the
+ hypothetical runtime_check module that this type is an unsigned int.
+ Every other consumer of this type can ignore this metadata and treat
+ this type as int.
+
+ The first argument to Annotated must be a valid type (and will be in
+ the __origin__ field), the remaining arguments are kept as a tuple in
+ the __extra__ field.
+
+ Details:
+
+ - It's an error to call `Annotated` with less than two arguments.
+ - Nested Annotated are flattened::
+
+ Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3]
+
+ - Instantiating an annotated type is equivalent to instantiating the
+ underlying type::
+
+ Annotated[C, Ann1](5) == C(5)
+
+ - Annotated can be used as a generic type alias::
+
+ Optimized = Annotated[T, runtime.Optimize()]
+ Optimized[int] == Annotated[int, runtime.Optimize()]
+
+ OptimizedList = Annotated[List[T], runtime.Optimize()]
+ OptimizedList[int] == Annotated[List[int], runtime.Optimize()]
+ """
+
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwargs):
+ raise TypeError("Type Annotated cannot be instantiated.")
+
+ @typing._tp_cache
+ def __class_getitem__(cls, params):
+ if not isinstance(params, tuple) or len(params) < 2:
+ raise TypeError("Annotated[...] should be used "
+ "with at least two arguments (a type and an "
+ "annotation).")
+ allowed_special_forms = (ClassVar, Final)
+ if get_origin(params[0]) in allowed_special_forms:
+ origin = params[0]
+ else:
+ msg = "Annotated[t, ...]: t must be a type."
+ origin = typing._type_check(params[0], msg)
+ metadata = tuple(params[1:])
+ return _AnnotatedAlias(origin, metadata)
+
+ def __init_subclass__(cls, *args, **kwargs):
+ raise TypeError(
+ f"Cannot subclass {cls.__module__}.Annotated"
+ )
+
+# Python 3.8 has get_origin() and get_args() but those implementations aren't
+# Annotated-aware, so we can't use those. Python 3.9's versions don't support
+# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do.
+if sys.version_info[:2] >= (3, 10):
+ get_origin = typing.get_origin
+ get_args = typing.get_args
+# 3.7-3.9
+else:
+ try:
+ # 3.9+
+ from typing import _BaseGenericAlias
+ except ImportError:
+ _BaseGenericAlias = typing._GenericAlias
+ try:
+ # 3.9+
+ from typing import GenericAlias as _typing_GenericAlias
+ except ImportError:
+ _typing_GenericAlias = typing._GenericAlias
+
+ def get_origin(tp):
+ """Get the unsubscripted version of a type.
+
+ This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar
+ and Annotated. Return None for unsupported types. Examples::
+
+ get_origin(Literal[42]) is Literal
+ get_origin(int) is None
+ get_origin(ClassVar[int]) is ClassVar
+ get_origin(Generic) is Generic
+ get_origin(Generic[T]) is Generic
+ get_origin(Union[T, int]) is Union
+ get_origin(List[Tuple[T, T]][int]) == list
+ get_origin(P.args) is P
+ """
+ if isinstance(tp, _AnnotatedAlias):
+ return Annotated
+ if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias, _BaseGenericAlias,
+ ParamSpecArgs, ParamSpecKwargs)):
+ return tp.__origin__
+ if tp is typing.Generic:
+ return typing.Generic
+ return None
+
+ def get_args(tp):
+ """Get type arguments with all substitutions performed.
+
+ For unions, basic simplifications used by Union constructor are performed.
+ Examples::
+ get_args(Dict[str, int]) == (str, int)
+ get_args(int) == ()
+ get_args(Union[int, Union[T, int], str][int]) == (int, str)
+ get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
+ get_args(Callable[[], T][int]) == ([], int)
+ """
+ if isinstance(tp, _AnnotatedAlias):
+ return (tp.__origin__,) + tp.__metadata__
+ if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias)):
+ if getattr(tp, "_special", False):
+ return ()
+ res = tp.__args__
+ if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis:
+ res = (list(res[:-1]), res[-1])
+ return res
+ return ()
+
+
+# 3.10+
+if hasattr(typing, 'TypeAlias'):
+ TypeAlias = typing.TypeAlias
+# 3.9
+elif sys.version_info[:2] >= (3, 9):
+ class _TypeAliasForm(typing._SpecialForm, _root=True):
+ def __repr__(self):
+ return 'typing_extensions.' + self._name
+
+ @_TypeAliasForm
+ def TypeAlias(self, parameters):
+ """Special marker indicating that an assignment should
+ be recognized as a proper type alias definition by type
+ checkers.
+
+ For example::
+
+ Predicate: TypeAlias = Callable[..., bool]
+
+ It's invalid when used anywhere except as in the example above.
+ """
+ raise TypeError(f"{self} is not subscriptable")
+# 3.7-3.8
+else:
+ class _TypeAliasForm(typing._SpecialForm, _root=True):
+ def __repr__(self):
+ return 'typing_extensions.' + self._name
+
+ TypeAlias = _TypeAliasForm('TypeAlias',
+ doc="""Special marker indicating that an assignment should
+ be recognized as a proper type alias definition by type
+ checkers.
+
+ For example::
+
+ Predicate: TypeAlias = Callable[..., bool]
+
+ It's invalid when used anywhere except as in the example
+ above.""")
+
+
+class _DefaultMixin:
+ """Mixin for TypeVarLike defaults."""
+
+ __slots__ = ()
+
+ def __init__(self, default):
+ if isinstance(default, (tuple, list)):
+ self.__default__ = tuple((typing._type_check(d, "Default must be a type")
+ for d in default))
+ elif default:
+ self.__default__ = typing._type_check(default, "Default must be a type")
+ else:
+ self.__default__ = None
+
+
+# Add default and infer_variance parameters from PEP 696 and 695
+class TypeVar(typing.TypeVar, _DefaultMixin, _root=True):
+ """Type variable."""
+
+ __module__ = 'typing'
+
+ def __init__(self, name, *constraints, bound=None,
+ covariant=False, contravariant=False,
+ default=None, infer_variance=False):
+ super().__init__(name, *constraints, bound=bound, covariant=covariant,
+ contravariant=contravariant)
+ _DefaultMixin.__init__(self, default)
+ self.__infer_variance__ = infer_variance
+
+ # for pickling:
+ try:
+ def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
+ except (AttributeError, ValueError):
+ def_mod = None
+ if def_mod != 'typing_extensions':
+ self.__module__ = def_mod
+
+
+# Python 3.10+ has PEP 612
+if hasattr(typing, 'ParamSpecArgs'):
+ ParamSpecArgs = typing.ParamSpecArgs
+ ParamSpecKwargs = typing.ParamSpecKwargs
+# 3.7-3.9
+else:
+ class _Immutable:
+ """Mixin to indicate that object should not be copied."""
+ __slots__ = ()
+
+ def __copy__(self):
+ return self
+
+ def __deepcopy__(self, memo):
+ return self
+
+ class ParamSpecArgs(_Immutable):
+ """The args for a ParamSpec object.
+
+ Given a ParamSpec object P, P.args is an instance of ParamSpecArgs.
+
+ ParamSpecArgs objects have a reference back to their ParamSpec:
+
+ P.args.__origin__ is P
+
+ This type is meant for runtime introspection and has no special meaning to
+ static type checkers.
+ """
+ def __init__(self, origin):
+ self.__origin__ = origin
+
+ def __repr__(self):
+ return f"{self.__origin__.__name__}.args"
+
+ def __eq__(self, other):
+ if not isinstance(other, ParamSpecArgs):
+ return NotImplemented
+ return self.__origin__ == other.__origin__
+
+ class ParamSpecKwargs(_Immutable):
+ """The kwargs for a ParamSpec object.
+
+ Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs.
+
+ ParamSpecKwargs objects have a reference back to their ParamSpec:
+
+ P.kwargs.__origin__ is P
+
+ This type is meant for runtime introspection and has no special meaning to
+ static type checkers.
+ """
+ def __init__(self, origin):
+ self.__origin__ = origin
+
+ def __repr__(self):
+ return f"{self.__origin__.__name__}.kwargs"
+
+ def __eq__(self, other):
+ if not isinstance(other, ParamSpecKwargs):
+ return NotImplemented
+ return self.__origin__ == other.__origin__
+
+# 3.10+
+if hasattr(typing, 'ParamSpec'):
+
+ # Add default Parameter - PEP 696
+ class ParamSpec(typing.ParamSpec, _DefaultMixin, _root=True):
+ """Parameter specification variable."""
+
+ __module__ = 'typing'
+
+ def __init__(self, name, *, bound=None, covariant=False, contravariant=False,
+ default=None):
+ super().__init__(name, bound=bound, covariant=covariant,
+ contravariant=contravariant)
+ _DefaultMixin.__init__(self, default)
+
+ # for pickling:
+ try:
+ def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
+ except (AttributeError, ValueError):
+ def_mod = None
+ if def_mod != 'typing_extensions':
+ self.__module__ = def_mod
+
+# 3.7-3.9
+else:
+
+ # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
+ class ParamSpec(list, _DefaultMixin):
+ """Parameter specification variable.
+
+ Usage::
+
+ P = ParamSpec('P')
+
+ Parameter specification variables exist primarily for the benefit of static
+ type checkers. They are used to forward the parameter types of one
+ callable to another callable, a pattern commonly found in higher order
+ functions and decorators. They are only valid when used in ``Concatenate``,
+ or s the first argument to ``Callable``. In Python 3.10 and higher,
+ they are also supported in user-defined Generics at runtime.
+ See class Generic for more information on generic types. An
+ example for annotating a decorator::
+
+ T = TypeVar('T')
+ P = ParamSpec('P')
+
+ def add_logging(f: Callable[P, T]) -> Callable[P, T]:
+ '''A type-safe decorator to add logging to a function.'''
+ def inner(*args: P.args, **kwargs: P.kwargs) -> T:
+ logging.info(f'{f.__name__} was called')
+ return f(*args, **kwargs)
+ return inner
+
+ @add_logging
+ def add_two(x: float, y: float) -> float:
+ '''Add two numbers together.'''
+ return x + y
+
+ Parameter specification variables defined with covariant=True or
+ contravariant=True can be used to declare covariant or contravariant
+ generic types. These keyword arguments are valid, but their actual semantics
+ are yet to be decided. See PEP 612 for details.
+
+ Parameter specification variables can be introspected. e.g.:
+
+ P.__name__ == 'T'
+ P.__bound__ == None
+ P.__covariant__ == False
+ P.__contravariant__ == False
+
+ Note that only parameter specification variables defined in global scope can
+ be pickled.
+ """
+
+ # Trick Generic __parameters__.
+ __class__ = typing.TypeVar
+
+ @property
+ def args(self):
+ return ParamSpecArgs(self)
+
+ @property
+ def kwargs(self):
+ return ParamSpecKwargs(self)
+
+ def __init__(self, name, *, bound=None, covariant=False, contravariant=False,
+ default=None):
+ super().__init__([self])
+ self.__name__ = name
+ self.__covariant__ = bool(covariant)
+ self.__contravariant__ = bool(contravariant)
+ if bound:
+ self.__bound__ = typing._type_check(bound, 'Bound must be a type.')
+ else:
+ self.__bound__ = None
+ _DefaultMixin.__init__(self, default)
+
+ # for pickling:
+ try:
+ def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
+ except (AttributeError, ValueError):
+ def_mod = None
+ if def_mod != 'typing_extensions':
+ self.__module__ = def_mod
+
+ def __repr__(self):
+ if self.__covariant__:
+ prefix = '+'
+ elif self.__contravariant__:
+ prefix = '-'
+ else:
+ prefix = '~'
+ return prefix + self.__name__
+
+ def __hash__(self):
+ return object.__hash__(self)
+
+ def __eq__(self, other):
+ return self is other
+
+ def __reduce__(self):
+ return self.__name__
+
+ # Hack to get typing._type_check to pass.
+ def __call__(self, *args, **kwargs):
+ pass
+
+
+# 3.7-3.9
+if not hasattr(typing, 'Concatenate'):
+ # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
+ class _ConcatenateGenericAlias(list):
+
+ # Trick Generic into looking into this for __parameters__.
+ __class__ = typing._GenericAlias
+
+ # Flag in 3.8.
+ _special = False
+
+ def __init__(self, origin, args):
+ super().__init__(args)
+ self.__origin__ = origin
+ self.__args__ = args
+
+ def __repr__(self):
+ _type_repr = typing._type_repr
+ return (f'{_type_repr(self.__origin__)}'
+ f'[{", ".join(_type_repr(arg) for arg in self.__args__)}]')
+
+ def __hash__(self):
+ return hash((self.__origin__, self.__args__))
+
+ # Hack to get typing._type_check to pass in Generic.
+ def __call__(self, *args, **kwargs):
+ pass
+
+ @property
+ def __parameters__(self):
+ return tuple(
+ tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec))
+ )
+
+
+# 3.7-3.9
+@typing._tp_cache
+def _concatenate_getitem(self, parameters):
+ if parameters == ():
+ raise TypeError("Cannot take a Concatenate of no types.")
+ if not isinstance(parameters, tuple):
+ parameters = (parameters,)
+ if not isinstance(parameters[-1], ParamSpec):
+ raise TypeError("The last parameter to Concatenate should be a "
+ "ParamSpec variable.")
+ msg = "Concatenate[arg, ...]: each arg must be a type."
+ parameters = tuple(typing._type_check(p, msg) for p in parameters)
+ return _ConcatenateGenericAlias(self, parameters)
+
+
+# 3.10+
+if hasattr(typing, 'Concatenate'):
+ Concatenate = typing.Concatenate
+ _ConcatenateGenericAlias = typing._ConcatenateGenericAlias # noqa
+# 3.9
+elif sys.version_info[:2] >= (3, 9):
+ @_TypeAliasForm
+ def Concatenate(self, parameters):
+ """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a
+ higher order function which adds, removes or transforms parameters of a
+ callable.
+
+ For example::
+
+ Callable[Concatenate[int, P], int]
+
+ See PEP 612 for detailed information.
+ """
+ return _concatenate_getitem(self, parameters)
+# 3.7-8
+else:
+ class _ConcatenateForm(typing._SpecialForm, _root=True):
+ def __repr__(self):
+ return 'typing_extensions.' + self._name
+
+ def __getitem__(self, parameters):
+ return _concatenate_getitem(self, parameters)
+
+ Concatenate = _ConcatenateForm(
+ 'Concatenate',
+ doc="""Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a
+ higher order function which adds, removes or transforms parameters of a
+ callable.
+
+ For example::
+
+ Callable[Concatenate[int, P], int]
+
+ See PEP 612 for detailed information.
+ """)
+
+# 3.10+
+if hasattr(typing, 'TypeGuard'):
+ TypeGuard = typing.TypeGuard
+# 3.9
+elif sys.version_info[:2] >= (3, 9):
+ class _TypeGuardForm(typing._SpecialForm, _root=True):
+ def __repr__(self):
+ return 'typing_extensions.' + self._name
+
+ @_TypeGuardForm
+ def TypeGuard(self, parameters):
+ """Special typing form used to annotate the return type of a user-defined
+ type guard function. ``TypeGuard`` only accepts a single type argument.
+ At runtime, functions marked this way should return a boolean.
+
+ ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static
+ type checkers to determine a more precise type of an expression within a
+ program's code flow. Usually type narrowing is done by analyzing
+ conditional code flow and applying the narrowing to a block of code. The
+ conditional expression here is sometimes referred to as a "type guard".
+
+ Sometimes it would be convenient to use a user-defined boolean function
+ as a type guard. Such a function should use ``TypeGuard[...]`` as its
+ return type to alert static type checkers to this intention.
+
+ Using ``-> TypeGuard`` tells the static type checker that for a given
+ function:
+
+ 1. The return value is a boolean.
+ 2. If the return value is ``True``, the type of its argument
+ is the type inside ``TypeGuard``.
+
+ For example::
+
+ def is_str(val: Union[str, float]):
+ # "isinstance" type guard
+ if isinstance(val, str):
+ # Type of ``val`` is narrowed to ``str``
+ ...
+ else:
+ # Else, type of ``val`` is narrowed to ``float``.
+ ...
+
+ Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower
+ form of ``TypeA`` (it can even be a wider form) and this may lead to
+ type-unsafe results. The main reason is to allow for things like
+ narrowing ``List[object]`` to ``List[str]`` even though the latter is not
+ a subtype of the former, since ``List`` is invariant. The responsibility of
+ writing type-safe type guards is left to the user.
+
+ ``TypeGuard`` also works with type variables. For more information, see
+ PEP 647 (User-Defined Type Guards).
+ """
+ item = typing._type_check(parameters, f'{self} accepts only a single type.')
+ return typing._GenericAlias(self, (item,))
+# 3.7-3.8
+else:
+ class _TypeGuardForm(typing._SpecialForm, _root=True):
+
+ def __repr__(self):
+ return 'typing_extensions.' + self._name
+
+ def __getitem__(self, parameters):
+ item = typing._type_check(parameters,
+ f'{self._name} accepts only a single type')
+ return typing._GenericAlias(self, (item,))
+
+ TypeGuard = _TypeGuardForm(
+ 'TypeGuard',
+ doc="""Special typing form used to annotate the return type of a user-defined
+ type guard function. ``TypeGuard`` only accepts a single type argument.
+ At runtime, functions marked this way should return a boolean.
+
+ ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static
+ type checkers to determine a more precise type of an expression within a
+ program's code flow. Usually type narrowing is done by analyzing
+ conditional code flow and applying the narrowing to a block of code. The
+ conditional expression here is sometimes referred to as a "type guard".
+
+ Sometimes it would be convenient to use a user-defined boolean function
+ as a type guard. Such a function should use ``TypeGuard[...]`` as its
+ return type to alert static type checkers to this intention.
+
+ Using ``-> TypeGuard`` tells the static type checker that for a given
+ function:
+
+ 1. The return value is a boolean.
+ 2. If the return value is ``True``, the type of its argument
+ is the type inside ``TypeGuard``.
+
+ For example::
+
+ def is_str(val: Union[str, float]):
+ # "isinstance" type guard
+ if isinstance(val, str):
+ # Type of ``val`` is narrowed to ``str``
+ ...
+ else:
+ # Else, type of ``val`` is narrowed to ``float``.
+ ...
+
+ Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower
+ form of ``TypeA`` (it can even be a wider form) and this may lead to
+ type-unsafe results. The main reason is to allow for things like
+ narrowing ``List[object]`` to ``List[str]`` even though the latter is not
+ a subtype of the former, since ``List`` is invariant. The responsibility of
+ writing type-safe type guards is left to the user.
+
+ ``TypeGuard`` also works with type variables. For more information, see
+ PEP 647 (User-Defined Type Guards).
+ """)
+
+
+# Vendored from cpython typing._SpecialFrom
+class _SpecialForm(typing._Final, _root=True):
+ __slots__ = ('_name', '__doc__', '_getitem')
+
+ def __init__(self, getitem):
+ self._getitem = getitem
+ self._name = getitem.__name__
+ self.__doc__ = getitem.__doc__
+
+ def __getattr__(self, item):
+ if item in {'__name__', '__qualname__'}:
+ return self._name
+
+ raise AttributeError(item)
+
+ def __mro_entries__(self, bases):
+ raise TypeError(f"Cannot subclass {self!r}")
+
+ def __repr__(self):
+ return f'typing_extensions.{self._name}'
+
+ def __reduce__(self):
+ return self._name
+
+ def __call__(self, *args, **kwds):
+ raise TypeError(f"Cannot instantiate {self!r}")
+
+ def __or__(self, other):
+ return typing.Union[self, other]
+
+ def __ror__(self, other):
+ return typing.Union[other, self]
+
+ def __instancecheck__(self, obj):
+ raise TypeError(f"{self} cannot be used with isinstance()")
+
+ def __subclasscheck__(self, cls):
+ raise TypeError(f"{self} cannot be used with issubclass()")
+
+ @typing._tp_cache
+ def __getitem__(self, parameters):
+ return self._getitem(self, parameters)
+
+
+if hasattr(typing, "LiteralString"):
+ LiteralString = typing.LiteralString
+else:
+ @_SpecialForm
+ def LiteralString(self, params):
+ """Represents an arbitrary literal string.
+
+ Example::
+
+ from pip._vendor.typing_extensions import LiteralString
+
+ def query(sql: LiteralString) -> ...:
+ ...
+
+ query("SELECT * FROM table") # ok
+ query(f"SELECT * FROM {input()}") # not ok
+
+ See PEP 675 for details.
+
+ """
+ raise TypeError(f"{self} is not subscriptable")
+
+
+if hasattr(typing, "Self"):
+ Self = typing.Self
+else:
+ @_SpecialForm
+ def Self(self, params):
+ """Used to spell the type of "self" in classes.
+
+ Example::
+
+ from typing import Self
+
+ class ReturnsSelf:
+ def parse(self, data: bytes) -> Self:
+ ...
+ return self
+
+ """
+
+ raise TypeError(f"{self} is not subscriptable")
+
+
+if hasattr(typing, "Never"):
+ Never = typing.Never
+else:
+ @_SpecialForm
+ def Never(self, params):
+ """The bottom type, a type that has no members.
+
+ This can be used to define a function that should never be
+ called, or a function that never returns::
+
+ from pip._vendor.typing_extensions import Never
+
+ def never_call_me(arg: Never) -> None:
+ pass
+
+ def int_or_str(arg: int | str) -> None:
+ never_call_me(arg) # type checker error
+ match arg:
+ case int():
+ print("It's an int")
+ case str():
+ print("It's a str")
+ case _:
+ never_call_me(arg) # ok, arg is of type Never
+
+ """
+
+ raise TypeError(f"{self} is not subscriptable")
+
+
+if hasattr(typing, 'Required'):
+ Required = typing.Required
+ NotRequired = typing.NotRequired
+elif sys.version_info[:2] >= (3, 9):
+ class _ExtensionsSpecialForm(typing._SpecialForm, _root=True):
+ def __repr__(self):
+ return 'typing_extensions.' + self._name
+
+ @_ExtensionsSpecialForm
+ def Required(self, parameters):
+ """A special typing construct to mark a key of a total=False TypedDict
+ as required. For example:
+
+ class Movie(TypedDict, total=False):
+ title: Required[str]
+ year: int
+
+ m = Movie(
+ title='The Matrix', # typechecker error if key is omitted
+ year=1999,
+ )
+
+ There is no runtime checking that a required key is actually provided
+ when instantiating a related TypedDict.
+ """
+ item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
+ return typing._GenericAlias(self, (item,))
+
+ @_ExtensionsSpecialForm
+ def NotRequired(self, parameters):
+ """A special typing construct to mark a key of a TypedDict as
+ potentially missing. For example:
+
+ class Movie(TypedDict):
+ title: str
+ year: NotRequired[int]
+
+ m = Movie(
+ title='The Matrix', # typechecker error if key is omitted
+ year=1999,
+ )
+ """
+ item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
+ return typing._GenericAlias(self, (item,))
+
+else:
+ class _RequiredForm(typing._SpecialForm, _root=True):
+ def __repr__(self):
+ return 'typing_extensions.' + self._name
+
+ def __getitem__(self, parameters):
+ item = typing._type_check(parameters,
+ f'{self._name} accepts only a single type.')
+ return typing._GenericAlias(self, (item,))
+
+ Required = _RequiredForm(
+ 'Required',
+ doc="""A special typing construct to mark a key of a total=False TypedDict
+ as required. For example:
+
+ class Movie(TypedDict, total=False):
+ title: Required[str]
+ year: int
+
+ m = Movie(
+ title='The Matrix', # typechecker error if key is omitted
+ year=1999,
+ )
+
+ There is no runtime checking that a required key is actually provided
+ when instantiating a related TypedDict.
+ """)
+ NotRequired = _RequiredForm(
+ 'NotRequired',
+ doc="""A special typing construct to mark a key of a TypedDict as
+ potentially missing. For example:
+
+ class Movie(TypedDict):
+ title: str
+ year: NotRequired[int]
+
+ m = Movie(
+ title='The Matrix', # typechecker error if key is omitted
+ year=1999,
+ )
+ """)
+
+
+if hasattr(typing, "Unpack"): # 3.11+
+ Unpack = typing.Unpack
+elif sys.version_info[:2] >= (3, 9):
+ class _UnpackSpecialForm(typing._SpecialForm, _root=True):
+ def __repr__(self):
+ return 'typing_extensions.' + self._name
+
+ class _UnpackAlias(typing._GenericAlias, _root=True):
+ __class__ = typing.TypeVar
+
+ @_UnpackSpecialForm
+ def Unpack(self, parameters):
+ """A special typing construct to unpack a variadic type. For example:
+
+ Shape = TypeVarTuple('Shape')
+ Batch = NewType('Batch', int)
+
+ def add_batch_axis(
+ x: Array[Unpack[Shape]]
+ ) -> Array[Batch, Unpack[Shape]]: ...
+
+ """
+ item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
+ return _UnpackAlias(self, (item,))
+
+ def _is_unpack(obj):
+ return isinstance(obj, _UnpackAlias)
+
+else:
+ class _UnpackAlias(typing._GenericAlias, _root=True):
+ __class__ = typing.TypeVar
+
+ class _UnpackForm(typing._SpecialForm, _root=True):
+ def __repr__(self):
+ return 'typing_extensions.' + self._name
+
+ def __getitem__(self, parameters):
+ item = typing._type_check(parameters,
+ f'{self._name} accepts only a single type.')
+ return _UnpackAlias(self, (item,))
+
+ Unpack = _UnpackForm(
+ 'Unpack',
+ doc="""A special typing construct to unpack a variadic type. For example:
+
+ Shape = TypeVarTuple('Shape')
+ Batch = NewType('Batch', int)
+
+ def add_batch_axis(
+ x: Array[Unpack[Shape]]
+ ) -> Array[Batch, Unpack[Shape]]: ...
+
+ """)
+
+ def _is_unpack(obj):
+ return isinstance(obj, _UnpackAlias)
+
+
+if hasattr(typing, "TypeVarTuple"): # 3.11+
+
+ # Add default Parameter - PEP 696
+ class TypeVarTuple(typing.TypeVarTuple, _DefaultMixin, _root=True):
+ """Type variable tuple."""
+
+ def __init__(self, name, *, default=None):
+ super().__init__(name)
+ _DefaultMixin.__init__(self, default)
+
+ # for pickling:
+ try:
+ def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
+ except (AttributeError, ValueError):
+ def_mod = None
+ if def_mod != 'typing_extensions':
+ self.__module__ = def_mod
+
+else:
+ class TypeVarTuple(_DefaultMixin):
+ """Type variable tuple.
+
+ Usage::
+
+ Ts = TypeVarTuple('Ts')
+
+ In the same way that a normal type variable is a stand-in for a single
+ type such as ``int``, a type variable *tuple* is a stand-in for a *tuple*
+ type such as ``Tuple[int, str]``.
+
+ Type variable tuples can be used in ``Generic`` declarations.
+ Consider the following example::
+
+ class Array(Generic[*Ts]): ...
+
+ The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``,
+ where ``T1`` and ``T2`` are type variables. To use these type variables
+ as type parameters of ``Array``, we must *unpack* the type variable tuple using
+ the star operator: ``*Ts``. The signature of ``Array`` then behaves
+ as if we had simply written ``class Array(Generic[T1, T2]): ...``.
+ In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows
+ us to parameterise the class with an *arbitrary* number of type parameters.
+
+ Type variable tuples can be used anywhere a normal ``TypeVar`` can.
+ This includes class definitions, as shown above, as well as function
+ signatures and variable annotations::
+
+ class Array(Generic[*Ts]):
+
+ def __init__(self, shape: Tuple[*Ts]):
+ self._shape: Tuple[*Ts] = shape
+
+ def get_shape(self) -> Tuple[*Ts]:
+ return self._shape
+
+ shape = (Height(480), Width(640))
+ x: Array[Height, Width] = Array(shape)
+ y = abs(x) # Inferred type is Array[Height, Width]
+ z = x + x # ... is Array[Height, Width]
+ x.get_shape() # ... is tuple[Height, Width]
+
+ """
+
+ # Trick Generic __parameters__.
+ __class__ = typing.TypeVar
+
+ def __iter__(self):
+ yield self.__unpacked__
+
+ def __init__(self, name, *, default=None):
+ self.__name__ = name
+ _DefaultMixin.__init__(self, default)
+
+ # for pickling:
+ try:
+ def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
+ except (AttributeError, ValueError):
+ def_mod = None
+ if def_mod != 'typing_extensions':
+ self.__module__ = def_mod
+
+ self.__unpacked__ = Unpack[self]
+
+ def __repr__(self):
+ return self.__name__
+
+ def __hash__(self):
+ return object.__hash__(self)
+
+ def __eq__(self, other):
+ return self is other
+
+ def __reduce__(self):
+ return self.__name__
+
+ def __init_subclass__(self, *args, **kwds):
+ if '_root' not in kwds:
+ raise TypeError("Cannot subclass special typing classes")
+
+
+if hasattr(typing, "reveal_type"):
+ reveal_type = typing.reveal_type
+else:
+ def reveal_type(__obj: T) -> T:
+ """Reveal the inferred type of a variable.
+
+ When a static type checker encounters a call to ``reveal_type()``,
+ it will emit the inferred type of the argument::
+
+ x: int = 1
+ reveal_type(x)
+
+ Running a static type checker (e.g., ``mypy``) on this example
+ will produce output similar to 'Revealed type is "builtins.int"'.
+
+ At runtime, the function prints the runtime type of the
+ argument and returns it unchanged.
+
+ """
+ print(f"Runtime type is {type(__obj).__name__!r}", file=sys.stderr)
+ return __obj
+
+
+if hasattr(typing, "assert_never"):
+ assert_never = typing.assert_never
+else:
+ def assert_never(__arg: Never) -> Never:
+ """Assert to the type checker that a line of code is unreachable.
+
+ Example::
+
+ def int_or_str(arg: int | str) -> None:
+ match arg:
+ case int():
+ print("It's an int")
+ case str():
+ print("It's a str")
+ case _:
+ assert_never(arg)
+
+ If a type checker finds that a call to assert_never() is
+ reachable, it will emit an error.
+
+ At runtime, this throws an exception when called.
+
+ """
+ raise AssertionError("Expected code to be unreachable")
+
+
+if hasattr(typing, 'dataclass_transform'):
+ dataclass_transform = typing.dataclass_transform
+else:
+ def dataclass_transform(
+ *,
+ eq_default: bool = True,
+ order_default: bool = False,
+ kw_only_default: bool = False,
+ field_specifiers: typing.Tuple[
+ typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]],
+ ...
+ ] = (),
+ **kwargs: typing.Any,
+ ) -> typing.Callable[[T], T]:
+ """Decorator that marks a function, class, or metaclass as providing
+ dataclass-like behavior.
+
+ Example:
+
+ from pip._vendor.typing_extensions import dataclass_transform
+
+ _T = TypeVar("_T")
+
+ # Used on a decorator function
+ @dataclass_transform()
+ def create_model(cls: type[_T]) -> type[_T]:
+ ...
+ return cls
+
+ @create_model
+ class CustomerModel:
+ id: int
+ name: str
+
+ # Used on a base class
+ @dataclass_transform()
+ class ModelBase: ...
+
+ class CustomerModel(ModelBase):
+ id: int
+ name: str
+
+ # Used on a metaclass
+ @dataclass_transform()
+ class ModelMeta(type): ...
+
+ class ModelBase(metaclass=ModelMeta): ...
+
+ class CustomerModel(ModelBase):
+ id: int
+ name: str
+
+ Each of the ``CustomerModel`` classes defined in this example will now
+ behave similarly to a dataclass created with the ``@dataclasses.dataclass``
+ decorator. For example, the type checker will synthesize an ``__init__``
+ method.
+
+ The arguments to this decorator can be used to customize this behavior:
+ - ``eq_default`` indicates whether the ``eq`` parameter is assumed to be
+ True or False if it is omitted by the caller.
+ - ``order_default`` indicates whether the ``order`` parameter is
+ assumed to be True or False if it is omitted by the caller.
+ - ``kw_only_default`` indicates whether the ``kw_only`` parameter is
+ assumed to be True or False if it is omitted by the caller.
+ - ``field_specifiers`` specifies a static list of supported classes
+ or functions that describe fields, similar to ``dataclasses.field()``.
+
+ At runtime, this decorator records its arguments in the
+ ``__dataclass_transform__`` attribute on the decorated object.
+
+ See PEP 681 for details.
+
+ """
+ def decorator(cls_or_fn):
+ cls_or_fn.__dataclass_transform__ = {
+ "eq_default": eq_default,
+ "order_default": order_default,
+ "kw_only_default": kw_only_default,
+ "field_specifiers": field_specifiers,
+ "kwargs": kwargs,
+ }
+ return cls_or_fn
+ return decorator
+
+
+if hasattr(typing, "override"):
+ override = typing.override
+else:
+ _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any])
+
+ def override(__arg: _F) -> _F:
+ """Indicate that a method is intended to override a method in a base class.
+
+ Usage:
+
+ class Base:
+ def method(self) -> None: ...
+ pass
+
+ class Child(Base):
+ @override
+ def method(self) -> None:
+ super().method()
+
+ When this decorator is applied to a method, the type checker will
+ validate that it overrides a method with the same name on a base class.
+ This helps prevent bugs that may occur when a base class is changed
+ without an equivalent change to a child class.
+
+ See PEP 698 for details.
+
+ """
+ return __arg
+
+
+# We have to do some monkey patching to deal with the dual nature of
+# Unpack/TypeVarTuple:
+# - We want Unpack to be a kind of TypeVar so it gets accepted in
+# Generic[Unpack[Ts]]
+# - We want it to *not* be treated as a TypeVar for the purposes of
+# counting generic parameters, so that when we subscript a generic,
+# the runtime doesn't try to substitute the Unpack with the subscripted type.
+if not hasattr(typing, "TypeVarTuple"):
+ typing._collect_type_vars = _collect_type_vars
+ typing._check_generic = _check_generic
+
+
+# Backport typing.NamedTuple as it exists in Python 3.11.
+# In 3.11, the ability to define generic `NamedTuple`s was supported.
+# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8.
+if sys.version_info >= (3, 11):
+ NamedTuple = typing.NamedTuple
+else:
+ def _caller():
+ try:
+ return sys._getframe(2).f_globals.get('__name__', '__main__')
+ except (AttributeError, ValueError): # For platforms without _getframe()
+ return None
+
+ def _make_nmtuple(name, types, module, defaults=()):
+ fields = [n for n, t in types]
+ annotations = {n: typing._type_check(t, f"field {n} annotation must be a type")
+ for n, t in types}
+ nm_tpl = collections.namedtuple(name, fields,
+ defaults=defaults, module=module)
+ nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = annotations
+ # The `_field_types` attribute was removed in 3.9;
+ # in earlier versions, it is the same as the `__annotations__` attribute
+ if sys.version_info < (3, 9):
+ nm_tpl._field_types = annotations
+ return nm_tpl
+
+ _prohibited_namedtuple_fields = typing._prohibited
+ _special_namedtuple_fields = frozenset({'__module__', '__name__', '__annotations__'})
+
+ class _NamedTupleMeta(type):
+ def __new__(cls, typename, bases, ns):
+ assert _NamedTuple in bases
+ for base in bases:
+ if base is not _NamedTuple and base is not typing.Generic:
+ raise TypeError(
+ 'can only inherit from a NamedTuple type and Generic')
+ bases = tuple(tuple if base is _NamedTuple else base for base in bases)
+ types = ns.get('__annotations__', {})
+ default_names = []
+ for field_name in types:
+ if field_name in ns:
+ default_names.append(field_name)
+ elif default_names:
+ raise TypeError(f"Non-default namedtuple field {field_name} "
+ f"cannot follow default field"
+ f"{'s' if len(default_names) > 1 else ''} "
+ f"{', '.join(default_names)}")
+ nm_tpl = _make_nmtuple(
+ typename, types.items(),
+ defaults=[ns[n] for n in default_names],
+ module=ns['__module__']
+ )
+ nm_tpl.__bases__ = bases
+ if typing.Generic in bases:
+ class_getitem = typing.Generic.__class_getitem__.__func__
+ nm_tpl.__class_getitem__ = classmethod(class_getitem)
+ # update from user namespace without overriding special namedtuple attributes
+ for key in ns:
+ if key in _prohibited_namedtuple_fields:
+ raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
+ elif key not in _special_namedtuple_fields and key not in nm_tpl._fields:
+ setattr(nm_tpl, key, ns[key])
+ if typing.Generic in bases:
+ nm_tpl.__init_subclass__()
+ return nm_tpl
+
+ def NamedTuple(__typename, __fields=None, **kwargs):
+ if __fields is None:
+ __fields = kwargs.items()
+ elif kwargs:
+ raise TypeError("Either list of fields or keywords"
+ " can be provided to NamedTuple, not both")
+ return _make_nmtuple(__typename, __fields, module=_caller())
+
+ NamedTuple.__doc__ = typing.NamedTuple.__doc__
+ _NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {})
+
+ # On 3.8+, alter the signature so that it matches typing.NamedTuple.
+ # The signature of typing.NamedTuple on >=3.8 is invalid syntax in Python 3.7,
+ # so just leave the signature as it is on 3.7.
+ if sys.version_info >= (3, 8):
+ NamedTuple.__text_signature__ = '(typename, fields=None, /, **kwargs)'
+
+ def _namedtuple_mro_entries(bases):
+ assert NamedTuple in bases
+ return (_NamedTuple,)
+
+ NamedTuple.__mro_entries__ = _namedtuple_mro_entries
diff --git a/src/pip/_vendor/typing_extensions.pyi b/src/pip/_vendor/typing_extensions.pyi
new file mode 100644
index 000000000..547f7a1b0
--- /dev/null
+++ b/src/pip/_vendor/typing_extensions.pyi
@@ -0,0 +1 @@
+from typing_extensions import * \ No newline at end of file
diff --git a/src/pip/_vendor/urllib3/__init__.py b/src/pip/_vendor/urllib3/__init__.py
index fe86b59d7..c6fa38212 100644
--- a/src/pip/_vendor/urllib3/__init__.py
+++ b/src/pip/_vendor/urllib3/__init__.py
@@ -19,6 +19,23 @@ from .util.retry import Retry
from .util.timeout import Timeout
from .util.url import get_host
+# === NOTE TO REPACKAGERS AND VENDORS ===
+# Please delete this block, this logic is only
+# for urllib3 being distributed via PyPI.
+# See: https://github.com/urllib3/urllib3/issues/2680
+try:
+ import urllib3_secure_extra # type: ignore # noqa: F401
+except ImportError:
+ pass
+else:
+ warnings.warn(
+ "'urllib3[secure]' extra is deprecated and will be removed "
+ "in a future release of urllib3 2.x. Read more in this issue: "
+ "https://github.com/urllib3/urllib3/issues/2680",
+ category=DeprecationWarning,
+ stacklevel=2,
+ )
+
__author__ = "Andrey Petrov (andrey.petrov@shazow.net)"
__license__ = "MIT"
__version__ = __version__
diff --git a/src/pip/_vendor/urllib3/_version.py b/src/pip/_vendor/urllib3/_version.py
index 5141d980b..7c031661b 100644
--- a/src/pip/_vendor/urllib3/_version.py
+++ b/src/pip/_vendor/urllib3/_version.py
@@ -1,2 +1,2 @@
# This file is protected via CODEOWNERS
-__version__ = "1.26.7"
+__version__ = "1.26.14"
diff --git a/src/pip/_vendor/urllib3/connection.py b/src/pip/_vendor/urllib3/connection.py
index 60f70f794..10fb36c4e 100644
--- a/src/pip/_vendor/urllib3/connection.py
+++ b/src/pip/_vendor/urllib3/connection.py
@@ -51,7 +51,6 @@ from .exceptions import (
SubjectAltNameWarning,
SystemTimeWarning,
)
-from .packages.ssl_match_hostname import CertificateError, match_hostname
from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection
from .util.ssl_ import (
assert_fingerprint,
@@ -61,6 +60,7 @@ from .util.ssl_ import (
resolve_ssl_version,
ssl_wrap_socket,
)
+from .util.ssl_match_hostname import CertificateError, match_hostname
log = logging.getLogger(__name__)
@@ -68,7 +68,7 @@ port_by_scheme = {"http": 80, "https": 443}
# When it comes time to update this value as a part of regular maintenance
# (ie test_recent_date is failing) update it to ~6 months before the current date.
-RECENT_DATE = datetime.date(2020, 7, 1)
+RECENT_DATE = datetime.date(2022, 1, 1)
_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]")
@@ -355,17 +355,15 @@ class HTTPSConnection(HTTPConnection):
def connect(self):
# Add certificate verification
- conn = self._new_conn()
+ self.sock = conn = self._new_conn()
hostname = self.host
tls_in_tls = False
if self._is_using_tunnel():
if self.tls_in_tls_required:
- conn = self._connect_tls_proxy(hostname, conn)
+ self.sock = conn = self._connect_tls_proxy(hostname, conn)
tls_in_tls = True
- self.sock = conn
-
# Calls self._set_hostport(), so self.host is
# self._tunnel_host below.
self._tunnel()
diff --git a/src/pip/_vendor/urllib3/connectionpool.py b/src/pip/_vendor/urllib3/connectionpool.py
index 8dccf4bc2..708739279 100644
--- a/src/pip/_vendor/urllib3/connectionpool.py
+++ b/src/pip/_vendor/urllib3/connectionpool.py
@@ -2,6 +2,7 @@ from __future__ import absolute_import
import errno
import logging
+import re
import socket
import sys
import warnings
@@ -35,7 +36,6 @@ from .exceptions import (
)
from .packages import six
from .packages.six.moves import queue
-from .packages.ssl_match_hostname import CertificateError
from .request import RequestMethods
from .response import HTTPResponse
from .util.connection import is_connection_dropped
@@ -44,6 +44,7 @@ from .util.queue import LifoQueue
from .util.request import set_file_position
from .util.response import assert_header_parsing
from .util.retry import Retry
+from .util.ssl_match_hostname import CertificateError
from .util.timeout import Timeout
from .util.url import Url, _encode_target
from .util.url import _normalize_host as normalize_host
@@ -301,8 +302,11 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
pass
except queue.Full:
# This should never happen if self.block == True
- log.warning("Connection pool is full, discarding connection: %s", self.host)
-
+ log.warning(
+ "Connection pool is full, discarding connection: %s. Connection pool size: %s",
+ self.host,
+ self.pool.qsize(),
+ )
# Connection never got put back into the pool, close it.
if conn:
conn.close()
@@ -745,7 +749,35 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
# Discard the connection for these exceptions. It will be
# replaced during the next _get_conn() call.
clean_exit = False
- if isinstance(e, (BaseSSLError, CertificateError)):
+
+ def _is_ssl_error_message_from_http_proxy(ssl_error):
+ # We're trying to detect the message 'WRONG_VERSION_NUMBER' but
+ # SSLErrors are kinda all over the place when it comes to the message,
+ # so we try to cover our bases here!
+ message = " ".join(re.split("[^a-z]", str(ssl_error).lower()))
+ return (
+ "wrong version number" in message or "unknown protocol" in message
+ )
+
+ # Try to detect a common user error with proxies which is to
+ # set an HTTP proxy to be HTTPS when it should be 'http://'
+ # (ie {'http': 'http://proxy', 'https': 'https://proxy'})
+ # Instead we add a nice error message and point to a URL.
+ if (
+ isinstance(e, BaseSSLError)
+ and self.proxy
+ and _is_ssl_error_message_from_http_proxy(e)
+ and conn.proxy
+ and conn.proxy.scheme == "https"
+ ):
+ e = ProxyError(
+ "Your proxy appears to only use HTTP and not HTTPS, "
+ "try changing your proxy URL to be HTTP. See: "
+ "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html"
+ "#https-proxy-error-http-proxy",
+ SSLError(e),
+ )
+ elif isinstance(e, (BaseSSLError, CertificateError)):
e = SSLError(e)
elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy:
e = ProxyError("Cannot connect to proxy.", e)
@@ -830,7 +862,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
)
# Check if we should retry the HTTP response.
- has_retry_after = bool(response.getheader("Retry-After"))
+ has_retry_after = bool(response.headers.get("Retry-After"))
if retries.is_retry(method, response.status, has_retry_after):
try:
retries = retries.increment(method, url, response=response, _pool=self)
diff --git a/src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py b/src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py
index 42526be7f..264d564db 100644
--- a/src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py
+++ b/src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py
@@ -48,7 +48,7 @@ from ctypes import (
)
from ctypes.util import find_library
-from pip._vendor.urllib3.packages.six import raise_from
+from ...packages.six import raise_from
if platform.system() != "Darwin":
raise ImportError("Only macOS is supported")
diff --git a/src/pip/_vendor/urllib3/contrib/appengine.py b/src/pip/_vendor/urllib3/contrib/appengine.py
index 668538695..1717ee22c 100644
--- a/src/pip/_vendor/urllib3/contrib/appengine.py
+++ b/src/pip/_vendor/urllib3/contrib/appengine.py
@@ -224,7 +224,7 @@ class AppEngineManager(RequestMethods):
)
# Check if we should retry the HTTP response.
- has_retry_after = bool(http_response.getheader("Retry-After"))
+ has_retry_after = bool(http_response.headers.get("Retry-After"))
if retries.is_retry(method, http_response.status, has_retry_after):
retries = retries.increment(method, url, response=http_response, _pool=self)
log.debug("Retry: %s", url)
diff --git a/src/pip/_vendor/urllib3/contrib/ntlmpool.py b/src/pip/_vendor/urllib3/contrib/ntlmpool.py
index 41a8fd174..471665754 100644
--- a/src/pip/_vendor/urllib3/contrib/ntlmpool.py
+++ b/src/pip/_vendor/urllib3/contrib/ntlmpool.py
@@ -69,7 +69,7 @@ class NTLMConnectionPool(HTTPSConnectionPool):
log.debug("Request headers: %s", headers)
conn.request("GET", self.authurl, None, headers)
res = conn.getresponse()
- reshdr = dict(res.getheaders())
+ reshdr = dict(res.headers)
log.debug("Response status: %s %s", res.status, res.reason)
log.debug("Response headers: %s", reshdr)
log.debug("Response data: %s [...]", res.read(100))
@@ -101,7 +101,7 @@ class NTLMConnectionPool(HTTPSConnectionPool):
conn.request("GET", self.authurl, None, headers)
res = conn.getresponse()
log.debug("Response status: %s %s", res.status, res.reason)
- log.debug("Response headers: %s", dict(res.getheaders()))
+ log.debug("Response headers: %s", dict(res.headers))
log.debug("Response data: %s [...]", res.read()[:100])
if res.status != 200:
if res.status == 401:
diff --git a/src/pip/_vendor/urllib3/contrib/pyopenssl.py b/src/pip/_vendor/urllib3/contrib/pyopenssl.py
index 3130f51ac..19e4aa97c 100644
--- a/src/pip/_vendor/urllib3/contrib/pyopenssl.py
+++ b/src/pip/_vendor/urllib3/contrib/pyopenssl.py
@@ -47,10 +47,10 @@ compression in Python 2 (see `CRIME attack`_).
"""
from __future__ import absolute_import
+import OpenSSL.crypto
import OpenSSL.SSL
from cryptography import x509
from cryptography.hazmat.backends.openssl import backend as openssl_backend
-from cryptography.hazmat.backends.openssl.x509 import _Certificate
try:
from cryptography.x509 import UnsupportedExtension
@@ -73,11 +73,20 @@ except ImportError: # Platform-specific: Python 3
import logging
import ssl
import sys
+import warnings
from .. import util
from ..packages import six
from ..util.ssl_ import PROTOCOL_TLS_CLIENT
+warnings.warn(
+ "'urllib3.contrib.pyopenssl' module is deprecated and will be removed "
+ "in a future release of urllib3 2.x. Read more in this issue: "
+ "https://github.com/urllib3/urllib3/issues/2680",
+ category=DeprecationWarning,
+ stacklevel=2,
+)
+
__all__ = ["inject_into_urllib3", "extract_from_urllib3"]
# SNI always works.
@@ -219,9 +228,8 @@ def get_subj_alt_name(peer_cert):
if hasattr(peer_cert, "to_cryptography"):
cert = peer_cert.to_cryptography()
else:
- # This is technically using private APIs, but should work across all
- # relevant versions before PyOpenSSL got a proper API for this.
- cert = _Certificate(openssl_backend, peer_cert._x509)
+ der = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, peer_cert)
+ cert = x509.load_der_x509_certificate(der, openssl_backend)
# We want to find the SAN extension. Ask Cryptography to locate it (it's
# faster than looping in Python)
@@ -406,7 +414,6 @@ if _fileobject: # Platform-specific: Python 2
self._makefile_refs += 1
return _fileobject(self, mode, bufsize, close=True)
-
else: # Platform-specific: Python 3
makefile = backport_makefile
diff --git a/src/pip/_vendor/urllib3/contrib/securetransport.py b/src/pip/_vendor/urllib3/contrib/securetransport.py
index b4ca80b88..4a06bc69d 100644
--- a/src/pip/_vendor/urllib3/contrib/securetransport.py
+++ b/src/pip/_vendor/urllib3/contrib/securetransport.py
@@ -770,7 +770,6 @@ if _fileobject: # Platform-specific: Python 2
self._makefile_refs += 1
return _fileobject(self, mode, bufsize, close=True)
-
else: # Platform-specific: Python 3
def makefile(self, mode="r", buffering=None, *args, **kwargs):
diff --git a/src/pip/_vendor/urllib3/packages/__init__.py b/src/pip/_vendor/urllib3/packages/__init__.py
index fce4caa65..e69de29bb 100644
--- a/src/pip/_vendor/urllib3/packages/__init__.py
+++ b/src/pip/_vendor/urllib3/packages/__init__.py
@@ -1,5 +0,0 @@
-from __future__ import absolute_import
-
-from . import ssl_match_hostname
-
-__all__ = ("ssl_match_hostname",)
diff --git a/src/pip/_vendor/urllib3/packages/six.py b/src/pip/_vendor/urllib3/packages/six.py
index ba50acb06..f099a3dcd 100644
--- a/src/pip/_vendor/urllib3/packages/six.py
+++ b/src/pip/_vendor/urllib3/packages/six.py
@@ -772,7 +772,6 @@ if PY3:
value = None
tb = None
-
else:
def exec_(_code_, _globs_=None, _locs_=None):
diff --git a/src/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py b/src/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py
deleted file mode 100644
index ef3fde520..000000000
--- a/src/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import sys
-
-try:
- # Our match_hostname function is the same as 3.10's, so we only want to
- # import the match_hostname function if it's at least that good.
- # We also fallback on Python 3.10+ because our code doesn't emit
- # deprecation warnings and is the same as Python 3.10 otherwise.
- if sys.version_info < (3, 5) or sys.version_info >= (3, 10):
- raise ImportError("Fallback to vendored code")
-
- from ssl import CertificateError, match_hostname
-except ImportError:
- try:
- # Backport of the function from a pypi module
- from backports.ssl_match_hostname import ( # type: ignore
- CertificateError,
- match_hostname,
- )
- except ImportError:
- # Our vendored copy
- from ._implementation import CertificateError, match_hostname # type: ignore
-
-# Not needed, but documenting what we provide.
-__all__ = ("CertificateError", "match_hostname")
diff --git a/src/pip/_vendor/urllib3/poolmanager.py b/src/pip/_vendor/urllib3/poolmanager.py
index 3a31a285b..ca4ec3411 100644
--- a/src/pip/_vendor/urllib3/poolmanager.py
+++ b/src/pip/_vendor/urllib3/poolmanager.py
@@ -34,6 +34,7 @@ SSL_KEYWORDS = (
"ca_cert_dir",
"ssl_context",
"key_password",
+ "server_hostname",
)
# All known keyword arguments that could be provided to the pool manager, its
diff --git a/src/pip/_vendor/urllib3/response.py b/src/pip/_vendor/urllib3/response.py
index 38693f4fc..8909f8454 100644
--- a/src/pip/_vendor/urllib3/response.py
+++ b/src/pip/_vendor/urllib3/response.py
@@ -2,16 +2,16 @@ from __future__ import absolute_import
import io
import logging
+import sys
+import warnings
import zlib
from contextlib import contextmanager
from socket import error as SocketError
from socket import timeout as SocketTimeout
-try:
- import brotli
-except ImportError:
- brotli = None
+brotli = None
+from . import util
from ._collections import HTTPHeaderDict
from .connection import BaseSSLError, HTTPException
from .exceptions import (
@@ -478,6 +478,54 @@ class HTTPResponse(io.IOBase):
if self._original_response and self._original_response.isclosed():
self.release_conn()
+ def _fp_read(self, amt):
+ """
+ Read a response with the thought that reading the number of bytes
+ larger than can fit in a 32-bit int at a time via SSL in some
+ known cases leads to an overflow error that has to be prevented
+ if `amt` or `self.length_remaining` indicate that a problem may
+ happen.
+
+ The known cases:
+ * 3.8 <= CPython < 3.9.7 because of a bug
+ https://github.com/urllib3/urllib3/issues/2513#issuecomment-1152559900.
+ * urllib3 injected with pyOpenSSL-backed SSL-support.
+ * CPython < 3.10 only when `amt` does not fit 32-bit int.
+ """
+ assert self._fp
+ c_int_max = 2 ** 31 - 1
+ if (
+ (
+ (amt and amt > c_int_max)
+ or (self.length_remaining and self.length_remaining > c_int_max)
+ )
+ and not util.IS_SECURETRANSPORT
+ and (util.IS_PYOPENSSL or sys.version_info < (3, 10))
+ ):
+ buffer = io.BytesIO()
+ # Besides `max_chunk_amt` being a maximum chunk size, it
+ # affects memory overhead of reading a response by this
+ # method in CPython.
+ # `c_int_max` equal to 2 GiB - 1 byte is the actual maximum
+ # chunk size that does not lead to an overflow error, but
+ # 256 MiB is a compromise.
+ max_chunk_amt = 2 ** 28
+ while amt is None or amt != 0:
+ if amt is not None:
+ chunk_amt = min(amt, max_chunk_amt)
+ amt -= chunk_amt
+ else:
+ chunk_amt = max_chunk_amt
+ data = self._fp.read(chunk_amt)
+ if not data:
+ break
+ buffer.write(data)
+ del data # to reduce peak memory usage by `max_chunk_amt`.
+ return buffer.getvalue()
+ else:
+ # StringIO doesn't like amt=None
+ return self._fp.read(amt) if amt is not None else self._fp.read()
+
def read(self, amt=None, decode_content=None, cache_content=False):
"""
Similar to :meth:`http.client.HTTPResponse.read`, but with two additional
@@ -510,13 +558,11 @@ class HTTPResponse(io.IOBase):
fp_closed = getattr(self._fp, "closed", False)
with self._error_catcher():
+ data = self._fp_read(amt) if not fp_closed else b""
if amt is None:
- # cStringIO doesn't like amt=None
- data = self._fp.read() if not fp_closed else b""
flush_decoder = True
else:
cache_content = False
- data = self._fp.read(amt) if not fp_closed else b""
if (
amt != 0 and not data
): # Platform-specific: Buggy versions of Python.
@@ -612,9 +658,21 @@ class HTTPResponse(io.IOBase):
# Backwards-compatibility methods for http.client.HTTPResponse
def getheaders(self):
+ warnings.warn(
+ "HTTPResponse.getheaders() is deprecated and will be removed "
+ "in urllib3 v2.1.0. Instead access HTTPResponse.headers directly.",
+ category=DeprecationWarning,
+ stacklevel=2,
+ )
return self.headers
def getheader(self, name, default=None):
+ warnings.warn(
+ "HTTPResponse.getheader() is deprecated and will be removed "
+ "in urllib3 v2.1.0. Instead use HTTPResponse.headers.get(name, default).",
+ category=DeprecationWarning,
+ stacklevel=2,
+ )
return self.headers.get(name, default)
# Backwards compatibility for http.cookiejar
diff --git a/src/pip/_vendor/urllib3/util/connection.py b/src/pip/_vendor/urllib3/util/connection.py
index facfa0dd2..6af1138f2 100644
--- a/src/pip/_vendor/urllib3/util/connection.py
+++ b/src/pip/_vendor/urllib3/util/connection.py
@@ -2,9 +2,8 @@ from __future__ import absolute_import
import socket
-from pip._vendor.urllib3.exceptions import LocationParseError
-
from ..contrib import _appengine_environ
+from ..exceptions import LocationParseError
from ..packages import six
from .wait import NoWayToWaitForSocketError, wait_for_read
diff --git a/src/pip/_vendor/urllib3/util/request.py b/src/pip/_vendor/urllib3/util/request.py
index 25103383e..330766ef4 100644
--- a/src/pip/_vendor/urllib3/util/request.py
+++ b/src/pip/_vendor/urllib3/util/request.py
@@ -13,12 +13,6 @@ SKIP_HEADER = "@@@SKIP_HEADER@@@"
SKIPPABLE_HEADERS = frozenset(["accept-encoding", "host", "user-agent"])
ACCEPT_ENCODING = "gzip,deflate"
-try:
- import brotli as _unused_module_brotli # noqa: F401
-except ImportError:
- pass
-else:
- ACCEPT_ENCODING += ",br"
_FAILEDTELL = object()
diff --git a/src/pip/_vendor/urllib3/util/retry.py b/src/pip/_vendor/urllib3/util/retry.py
index c7dc42f1d..2490d5e5b 100644
--- a/src/pip/_vendor/urllib3/util/retry.py
+++ b/src/pip/_vendor/urllib3/util/retry.py
@@ -69,6 +69,24 @@ class _RetryMeta(type):
)
cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT = value
+ @property
+ def BACKOFF_MAX(cls):
+ warnings.warn(
+ "Using 'Retry.BACKOFF_MAX' is deprecated and "
+ "will be removed in v2.0. Use 'Retry.DEFAULT_BACKOFF_MAX' instead",
+ DeprecationWarning,
+ )
+ return cls.DEFAULT_BACKOFF_MAX
+
+ @BACKOFF_MAX.setter
+ def BACKOFF_MAX(cls, value):
+ warnings.warn(
+ "Using 'Retry.BACKOFF_MAX' is deprecated and "
+ "will be removed in v2.0. Use 'Retry.DEFAULT_BACKOFF_MAX' instead",
+ DeprecationWarning,
+ )
+ cls.DEFAULT_BACKOFF_MAX = value
+
@six.add_metaclass(_RetryMeta)
class Retry(object):
@@ -181,7 +199,7 @@ class Retry(object):
seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep
for [0.0s, 0.2s, 0.4s, ...] between retries. It will never be longer
- than :attr:`Retry.BACKOFF_MAX`.
+ than :attr:`Retry.DEFAULT_BACKOFF_MAX`.
By default, backoff is disabled (set to 0).
@@ -220,7 +238,7 @@ class Retry(object):
DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Authorization"])
#: Maximum backoff time.
- BACKOFF_MAX = 120
+ DEFAULT_BACKOFF_MAX = 120
def __init__(
self,
@@ -348,7 +366,7 @@ class Retry(object):
return 0
backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1))
- return min(self.BACKOFF_MAX, backoff_value)
+ return min(self.DEFAULT_BACKOFF_MAX, backoff_value)
def parse_retry_after(self, retry_after):
# Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4
@@ -376,7 +394,7 @@ class Retry(object):
def get_retry_after(self, response):
"""Get the value of Retry-After in seconds."""
- retry_after = response.getheader("Retry-After")
+ retry_after = response.headers.get("Retry-After")
if retry_after is None:
return None
diff --git a/src/pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py b/src/pip/_vendor/urllib3/util/ssl_match_hostname.py
index 689208d3c..1dd950c48 100644
--- a/src/pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py
+++ b/src/pip/_vendor/urllib3/util/ssl_match_hostname.py
@@ -9,7 +9,7 @@ import sys
# ipaddress has been backported to 2.6+ in pypi. If it is installed on the
# system, use it to handle IPAddress ServerAltnames (this was added in
# python-3.5) otherwise only do DNS matching. This allows
-# backports.ssl_match_hostname to continue to be used in Python 2.7.
+# util.ssl_match_hostname to continue to be used in Python 2.7.
try:
import ipaddress
except ImportError:
@@ -78,7 +78,8 @@ def _dnsname_match(dn, hostname, max_wildcards=1):
def _to_unicode(obj):
if isinstance(obj, str) and sys.version_info < (3,):
- obj = unicode(obj, encoding="ascii", errors="strict")
+ # ignored flake8 # F821 to support python 2.7 function
+ obj = unicode(obj, encoding="ascii", errors="strict") # noqa: F821
return obj
@@ -111,11 +112,9 @@ def match_hostname(cert, hostname):
try:
# Divergence from upstream: ipaddress can't handle byte str
host_ip = ipaddress.ip_address(_to_unicode(hostname))
- except ValueError:
- # Not an IP address (common case)
- host_ip = None
- except UnicodeError:
- # Divergence from upstream: Have to deal with ipaddress not taking
+ except (UnicodeError, ValueError):
+ # ValueError: Not an IP address (common case)
+ # UnicodeError: Divergence from upstream: Have to deal with ipaddress not taking
# byte strings. addresses should be all ascii, so we consider it not
# an ipaddress in this case
host_ip = None
@@ -123,7 +122,7 @@ def match_hostname(cert, hostname):
# Divergence from upstream: Make ipaddress library optional
if ipaddress is None:
host_ip = None
- else:
+ else: # Defensive
raise
dnsnames = []
san = cert.get("subjectAltName", ())
diff --git a/src/pip/_vendor/urllib3/util/ssltransport.py b/src/pip/_vendor/urllib3/util/ssltransport.py
index 0ed97b644..4a7105d17 100644
--- a/src/pip/_vendor/urllib3/util/ssltransport.py
+++ b/src/pip/_vendor/urllib3/util/ssltransport.py
@@ -2,8 +2,8 @@ import io
import socket
import ssl
-from pip._vendor.urllib3.exceptions import ProxySchemeUnsupported
-from pip._vendor.urllib3.packages import six
+from ..exceptions import ProxySchemeUnsupported
+from ..packages import six
SSL_BLOCKSIZE = 16384
diff --git a/src/pip/_vendor/urllib3/util/url.py b/src/pip/_vendor/urllib3/util/url.py
index 3651c4318..d6d0bbcea 100644
--- a/src/pip/_vendor/urllib3/util/url.py
+++ b/src/pip/_vendor/urllib3/util/url.py
@@ -63,7 +63,7 @@ IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT + "$")
BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT[2:-2] + "$")
ZONE_ID_RE = re.compile("(" + ZONE_ID_PAT + r")\]$")
-_HOST_PORT_PAT = ("^(%s|%s|%s)(?::([0-9]{0,5}))?$") % (
+_HOST_PORT_PAT = ("^(%s|%s|%s)(?::0*?(|0|[1-9][0-9]{0,4}))?$") % (
REG_NAME_PAT,
IPV4_PAT,
IPV6_ADDRZ_PAT,
@@ -279,6 +279,9 @@ def _normalize_host(host, scheme):
if scheme in NORMALIZABLE_SCHEMES:
is_ipv6 = IPV6_ADDRZ_RE.match(host)
if is_ipv6:
+ # IPv6 hosts of the form 'a::b%zone' are encoded in a URL as
+ # such per RFC 6874: 'a::b%25zone'. Unquote the ZoneID
+ # separator as necessary to return a valid RFC 4007 scoped IP.
match = ZONE_ID_RE.search(host)
if match:
start, end = match.span(1)
@@ -331,7 +334,7 @@ def parse_url(url):
"""
Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is
performed to parse incomplete urls. Fields not provided will be None.
- This parser is RFC 3986 compliant.
+ This parser is RFC 3986 and RFC 6874 compliant.
The parser logic and helper functions are based heavily on
work done in the ``rfc3986`` module.
diff --git a/src/pip/_vendor/urllib3/util/wait.py b/src/pip/_vendor/urllib3/util/wait.py
index c280646c7..21b4590b3 100644
--- a/src/pip/_vendor/urllib3/util/wait.py
+++ b/src/pip/_vendor/urllib3/util/wait.py
@@ -42,7 +42,6 @@ if sys.version_info >= (3, 5):
def _retry_on_intr(fn, timeout):
return fn(timeout)
-
else:
# Old and broken Pythons.
def _retry_on_intr(fn, timeout):
diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt
index bf8ebcf55..8f11ce953 100644
--- a/src/pip/_vendor/vendor.txt
+++ b/src/pip/_vendor/vendor.txt
@@ -1,22 +1,23 @@
-CacheControl==0.12.6 # Make sure to update the license in pyproject.toml for this.
-colorama==0.4.4
-distlib==0.3.3
-distro==1.6.0
-html5lib==1.1
-msgpack==1.0.2
-packaging==21.0
-pep517==0.12.0
-platformdirs==2.4.0
-progress==1.6
-pyparsing==2.4.7
-requests==2.26.0
- certifi==2021.05.30
- chardet==4.0.0
- idna==3.2
- urllib3==1.26.7
-resolvelib==0.8.1
-setuptools==44.0.0
+CacheControl==0.12.11 # Make sure to update the license in pyproject.toml for this.
+colorama==0.4.6
+distlib==0.3.6
+distro==1.8.0
+msgpack==1.0.4
+packaging==21.3
+platformdirs==2.6.2
+pyparsing==3.0.9
+pyproject-hooks==1.0.0
+requests==2.28.2
+ certifi==2022.12.7
+ chardet==5.1.0
+ idna==3.4
+ urllib3==1.26.14
+rich==12.6.0
+ pygments==2.13.0
+ typing_extensions==4.4.0
+resolvelib==0.9.0
+setuptools==65.6.3
six==1.16.0
-tenacity==8.0.1
-tomli==1.0.3
+tenacity==8.1.0
+tomli==2.0.1
webencodings==0.5.1